123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 |
- ;;; client-server.scm -- Guile-SSH client is SUT.
- ;; Copyright (C) 2014, 2015, 2016 Artyom V. Poptsov <poptsov.artyom@gmail.com>
- ;;
- ;; This file is a part of Guile-SSH.
- ;;
- ;; Guile-SSH is free software: you can redistribute it and/or
- ;; modify it under the terms of the GNU General Public License as
- ;; published by the Free Software Foundation, either version 3 of the
- ;; License, or (at your option) any later version.
- ;;
- ;; Guile-SSH 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
- ;; General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU General Public License
- ;; along with Guile-SSH. If not, see <http://www.gnu.org/licenses/>.
- (add-to-load-path (getenv "abs_top_srcdir"))
- (use-modules (srfi srfi-64)
- (srfi srfi-26)
- (ice-9 threads)
- (ice-9 rdelim)
- (ice-9 regex)
- (rnrs bytevectors)
- (rnrs io ports)
- (ssh server)
- (ssh session)
- (ssh auth)
- (ssh message)
- (ssh key)
- (ssh channel)
- (ssh log)
- (ssh tunnel)
- (srfi srfi-4)
- (tests common))
- (test-begin-with-log "client-server")
- ;;; Global symbols
- (define topdir (getenv "abs_top_srcdir"))
- (define log (test-runner-aux-value (test-runner-current)))
- (define *server-thread* #f)
- ;;; Helper procedures and macros
- (define (srvmsg message)
- "Print a server MESSAGE to the test log."
- (format log " server: ~a~%" message))
- ;;; Testing of basic procedures.
- ;; Helper procedures.
- (define (simple-server-proc server)
- "start a SERVER that accepts a connection and handles a key exchange."
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; Tests.
- (test-assert-with-log "connect!, disconnect!"
- (run-client-test
- ;; server
- (lambda (server)
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (connected? session))))))
- (test-equal-with-log "get-protocol-version"
- 2
- (run-client-test
- ;; server
- (lambda (server)
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (get-protocol-version session))))))
- (test-assert-with-log "authenticate-server, not-known"
- 'not-known
- (run-client-test
- ;; server
- (lambda (server)
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session))))))
- (test-equal-with-log "authenticate-server, ok"
- 'ok
- (run-client-test
- ;; server
- (lambda (server)
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; client
- (lambda ()
- (let ((res (call-with-connected-session
- (lambda (session)
- (write-known-host! session)
- (authenticate-server session)))))
- (delete-file %knownhosts)
- res))))
- (test-assert-with-log "get-public-key-hash"
- (run-client-test
- ;; server
- (lambda (server)
- (let ((s (server-accept server)))
- (server-handle-key-exchange s)))
- ;; client
- (lambda ()
- (let ((hash-md5-bv #vu8(15 142 110 203 162 228 250 211 20 212 26 217 118 57 217 66))
- (hash-md5-str "0f:8e:6e:cb:a2:e4:fa:d3:14:d4:1a:d9:76:39:d9:42")
- (hash-sha1-bv #vu8(20 65 56 155 119 45 84 163 50 26 59 92 215 159 139 5 229 174 84 80))
- (hash-sha1-str "14:41:38:9b:77:2d:54:a3:32:1a:3b:5c:d7:9f:8b:05:e5:ae:54:50")
- (session (make-session-for-test)))
- (sleep 1)
- (connect! session)
- (authenticate-server session)
- (let* ((pubkey (get-server-public-key session))
- (md5-res (get-public-key-hash pubkey 'md5))
- (sha1-res (get-public-key-hash pubkey 'sha1)))
- (disconnect! session)
- (and (bytevector=? md5-res hash-md5-bv)
- (string=? (bytevector->hex-string md5-res) hash-md5-str)
- (bytevector=? sha1-res hash-sha1-bv)
- (string=? (bytevector->hex-string sha1-res) hash-sha1-str)))))))
- ;;;
- ;;; Authentication
- ;;;
- ;;; 'userauth-none!'
- ;; The procedure called with a wrong object as a parameter which leads to an
- ;; exception.
- (test-error-with-log "userauth-none!, wrong parameter" 'wrong-type-arg
- (userauth-none! "Not a session."))
- ;; Client tries to authenticate using a non-connected session which leads to
- ;; an exception.
- (test-error-with-log "userauth-none!, not connected" 'wrong-type-arg
- (userauth-none! (make-session-for-test)))
- ;; Server replies with "success", client receives 'success.
- (test-equal-with-log "userauth-none!, success"
- 'success
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(none))
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-none! session))))))
- ;; Server replies with "default", client receives 'denied.
- (test-equal-with-log "userauth-none!, denied"
- 'denied
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(public-key))
- (message-reply-default msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-none! session))))))
- ;; Server replies with "partial success", client receives 'partial.
- (test-equal-with-log "userauth-none!, partial"
- 'partial
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(none))
- (message-reply-success msg 'partial)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-none! session))))))
- ;;; 'userauth-password!'
- ;; The procedure called with a wrong object as a parameter which leads to an
- ;; exception.
- (test-error-with-log "userauth-password!, session: non-session object"
- 'wrong-type-arg
- (userauth-password! "Not a session." "Password"))
- ;; Client tries to authenticate using a non-connected session which leads to
- ;; an exception.
- (test-error-with-log "userauth-password!, session: non-connected session"
- 'wrong-type-arg
- (userauth-password! (make-session-for-test) "Password"))
- ;; User tries to authenticate using a non-string object as a password. the
- ;; procedure raises an error.
- (test-error-with-log "userauth-password!, password: non-string object"
- 'wrong-type-arg
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(password))
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (userauth-password! session 123))))))
- (test-equal-with-log "userauth-password!, success"
- 'success
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(password))
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-password! session "password"))))))
- (test-equal-with-log "userauth-password!, denied"
- 'denied
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(password))
- (message-reply-default msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-password! session "password"))))))
- (test-equal-with-log "userauth-password!, partial"
- 'partial
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(password))
- (message-reply-success msg 'partial)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-password! session "password"))))))
- ;;; 'userauth-public-key!'
- ;; The procedure called with a wrong object as a parameter which leads to an
- ;; exception.
- (test-error-with-log "userauth-public-key!, wrong parameter" 'wrong-type-arg
- (userauth-public-key! "Not a session." (private-key-from-file %rsakey)))
- ;; Client tries to authenticate using a non-connected session which leads to
- ;; an exception.
- (test-error-with-log "userauth-public-key!, non-connected session"
- 'wrong-type-arg
- (userauth-public-key! (make-session-for-test)
- (private-key-from-file %rsakey)))
- ;; Client tries to use a non-key object for authentication, the procedure
- ;; raises an exception.
- (test-error-with-log "userauth-public-key!, private-key: non-key object"
- 'wrong-type-arg
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (userauth-public-key! session "Non-key object."))))))
- ;; Client tries to use a public key for authentication, the procedure raises
- ;; an exception.
- (test-error-with-log "userauth-public-key!, private-key: public key"
- 'wrong-type-arg
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (userauth-public-key! session (public-key-from-file %rsakey-pub)))))))
- (test-equal-with-log "userauth-public-key!, success"
- 'success
- (run-client-test
- ;; server
- (lambda (server)
- (server-listen server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-reply-success msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (let ((prvkey (private-key-from-file %rsakey)))
- (userauth-public-key! session prvkey)))))))
- ;;; 'userauth-public-key/auto!'
- ;; The procedure called with a wrong object as a parameter which leads to an
- ;; exception.
- (test-error-with-log "userauth-public-key/auto!, session: non-session object"
- 'wrong-type-arg
- (userauth-public-key/auto! "Not a session."))
- ;; Client tries to authenticate using a non-connected session which leads to
- ;; an exception.
- (test-error-with-log "userauth-public-key/auto!, session: non-connected session"
- 'wrong-type-arg
- (userauth-public-key/auto! (make-session-for-test)))
- ;;;
- ;; The procedure called with a wrong object as a parameter which leads to an
- ;; exception.
- (test-error-with-log "userauth-get-list, wrong parameter" 'wrong-type-arg
- (userauth-get-list "Not a session."))
- (test-error-with-log "userauth-get-list, non-connected" 'wrong-type-arg
- (userauth-get-list (make-session-for-test)))
- ;; Server replies "default" with the list of allowed authentication
- ;; methods. Client receives the list.
- (test-equal-with-log "userauth-get-list"
- '(password public-key)
- (run-client-test
- ;; server
- (lambda (server)
- (let ((session (server-accept server)))
- (server-handle-key-exchange session)
- (start-session-loop session
- (lambda (msg)
- (message-auth-set-methods! msg '(password public-key))
- (message-reply-default msg)))))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-none! session)
- (userauth-get-list session))))))
- ;;; Channel test
- ;; make, open, exec
- ;; TODO: Fix the bug: the procedure cannot be used to test errors.
- (define (call-with-connected-session/channel-test proc)
- (define max-tries 30)
- (define (loop count)
- (catch #t
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (format-log/scm 'nolog
- "call-with-connected-session/channel-test"
- "connected in ~d tries: ~a" count session)
- (let ((result (authenticate-server session)))
- (format-log/scm 'nolog
- "call-with-connected-session/channel-test"
- "server authentication result: ~a" result)
- (when (equal? result 'error)
- (error "Could not authenticate server" session result)))
- (let ((result (userauth-none! session)))
- (format-log/scm 'nolog
- "call-with-connected-session/channel-test"
- "client authentication result: ~a" result))
- ;; (unless (equal? result 'ok)
- ;; (error "Could not authenticate client" session result)))
- (proc session))))
- (lambda args
- (format-log/scm 'nolog
- "make-session/channel-test"
- "Unable to connect in ~d tries~%"
- count)
- (sleep 1)
- (if (= count max-tries)
- (format-log/scm 'nolog
- "make-session/channel-test"
- "~a"
- "Giving up ...")
- (loop (1+ count))))))
- (loop 1))
- (test-assert-with-log "make-channel"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- make-channel))))
- (test-assert-with-log "channel-get-session"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel session)))
- (eq? session (channel-get-session channel))))))))
- (test-assert-with-log "channel-open-session"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (format-log/scm 'nolog "channel-open-session [client]"
- "session: ~a" session)
- (let ((channel (make-channel session)))
- (format-log/scm 'nolog "channel-open-session [client]"
- "channel: ~a" channel)
- (channel-open-session channel)
- (format-log/scm 'nolog "channel-open-session [client]"
- "channel 2: ~a" channel)
- (not (port-closed? channel))))))))
- ;; Client sends "ping" as a command to execute, server replies with "pong"
- (test-assert-with-log "channel-request-exec"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel session)))
- (channel-open-session channel)
- (channel-request-exec channel "ping")
- (let ((res (read-line channel)))
- (and res
- (string=? "pong" res)))))))))
- ;; Client sends "uname" as a command to execute, server returns exit status 0.
- (test-assert-with-log "channel-request-exec, exit status"
- 0
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel session)))
- (channel-open-session channel)
- (channel-request-exec channel "exit status")
- (channel-get-exit-status channel)))))))
- (test-assert-with-log "channel-request-exec, printing a freed channel"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel session)))
- (format-log/scm 'nolog "channel-request-exec, printing a freed channel"
- "channel 0: ~a" channel)
- (channel-open-session channel)
- (format-log/scm 'nolog "channel-request-exec, printing a freed channel"
- "channel 1: ~a" channel)
- (channel-request-exec channel "exit status")
- (format-log/scm 'nolog "channel-request-exec, printing a freed channel"
- "channel 2: ~a" channel)
- (close channel)
- (format-log/scm 'nolog "channel-request-exec, printing a freed channel"
- "channel: ~a" channel)
- (string-match "#<unknown channel \\(freed\\) [0-9a-f]+>"
- (object->string channel))))))))
- (test-error-with-log "channel-get-exit-status, freed channel"
- 'wrong-type-arg
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/exec server (const #t)))
- ;; client
- (lambda ()
- (call-with-connected-session
- (lambda (session)
- (authenticate-server session)
- (userauth-none! session)
- (let ((channel (make-channel session)))
- (channel-open-session channel)
- (channel-request-exec channel "exit status")
- (close channel)
- (channel-get-exit-status channel)))))))
- ;; data transferring
- ;; FIXME: Probably these TCs can be implemented more elegantly.
- (define (make-channel/dt-test session)
- (let ((c (make-channel session)))
- (channel-open-session c)
- c))
- (test-assert-with-log "data transferring, string"
- (run-client-test
- ;; server
- (lambda (server)
- (start-server/dt-test server
- (lambda (channel)
- (let ((str (read-line channel)))
- (write-line str channel)))))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel/dt-test session))
- (str "Hello Scheme World!"))
- (write-line str channel)
- (poll channel
- (lambda args
- (let ((res (read-line channel)))
- (disconnect! session)
- (equal? res str))))))))))
- (test-assert-with-log "data transferring, bytevector"
- (run-client-test
- ;; server
- (lambda (server)
- (use-modules (rnrs bytevectors)
- (rnrs io ports))
- (start-server/dt-test server
- (lambda (channel)
- (let ((v (get-bytevector-n channel 10)))
- (put-bytevector channel v)))))
- ;; client
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let* ((vect-size 10)
- (channel (make-channel/dt-test session))
- (vect (make-bytevector vect-size 42)))
- (format-log/scm 'nolog
- "data transferring, bytevector"
- "vect: ~a" vect)
- (put-bytevector channel vect)
- (poll channel
- (lambda args
- (let ((res (get-bytevector-n channel vect-size)))
- (format-log/scm 'nolog
- "data transferring, bytevector"
- "res: ~a" res)
- (equal? res vect))))))))))
- ;;;
- ;;; Channels
- ;;;
- ;; Client opens a channel to a server, sends data and then sends EOF on the
- ;; channel. Server reads data and sends it back. Client checks if the
- ;; channel is closed for output, and reads the data.
- (test-assert-with-log "channel-send-eof"
- (run-client-test
- (lambda (server)
- (start-server/dt-test server
- (lambda (channel)
- (let ((str (read-line channel)))
- (write-line str channel)))))
- (lambda ()
- (call-with-connected-session/channel-test
- (lambda (session)
- (let ((channel (make-channel/dt-test session))
- (str "Hello Scheme World!"))
- (write-line str channel)
- (channel-send-eof channel)
- (and (input-port? channel)
- (not (output-port? channel))
- (string=? (read-line channel) str))))))))
- ;;;
- (test-end "client-server")
- (exit (= (test-runner-fail-count (test-runner-current)) 0))
- ;;; client-server.scm ends here.
|