client-server.scm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. ;;; client-server.scm -- Guile-SSH client is SUT.
  2. ;; Copyright (C) 2014, 2015, 2016 Artyom V. Poptsov <poptsov.artyom@gmail.com>
  3. ;;
  4. ;; This file is a part of Guile-SSH.
  5. ;;
  6. ;; Guile-SSH is free software: you can redistribute it and/or
  7. ;; modify it under the terms of the GNU General Public License as
  8. ;; published by the Free Software Foundation, either version 3 of the
  9. ;; License, or (at your option) any later version.
  10. ;;
  11. ;; Guile-SSH is distributed in the hope that it will be useful, but
  12. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;; General Public License for more details.
  15. ;;
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with Guile-SSH. If not, see <http://www.gnu.org/licenses/>.
  18. (add-to-load-path (getenv "abs_top_srcdir"))
  19. (use-modules (srfi srfi-64)
  20. (srfi srfi-26)
  21. (ice-9 threads)
  22. (ice-9 rdelim)
  23. (rnrs bytevectors)
  24. (rnrs io ports)
  25. (ssh server)
  26. (ssh session)
  27. (ssh auth)
  28. (ssh message)
  29. (ssh key)
  30. (ssh channel)
  31. (ssh log)
  32. (ssh tunnel)
  33. (srfi srfi-4)
  34. (tests common))
  35. (test-begin-with-log "client-server")
  36. ;;; Global symbols
  37. (define topdir (getenv "abs_top_srcdir"))
  38. (define log (test-runner-aux-value (test-runner-current)))
  39. (define *server-thread* #f)
  40. ;;; Helper procedures and macros
  41. (define (srvmsg message)
  42. "Print a server MESSAGE to the test log."
  43. (format log " server: ~a~%" message))
  44. ;;; Testing of basic procedures.
  45. ;; Helper procedures.
  46. (define (simple-server-proc server)
  47. "start a SERVER that accepts a connection and handles a key exchange."
  48. (let ((s (server-accept server)))
  49. (server-handle-key-exchange s)))
  50. ;; Tests.
  51. (test-assert-with-log "connect!, disconnect!"
  52. (run-client-test
  53. ;; server
  54. simple-server-proc
  55. ;; client
  56. (lambda ()
  57. (call-with-connected-session
  58. (lambda (session)
  59. (connected? session))))))
  60. (test-equal-with-log "get-protocol-version"
  61. 2
  62. (run-client-test
  63. ;; server
  64. simple-server-proc
  65. ;; client
  66. (lambda ()
  67. (call-with-connected-session
  68. (lambda (session)
  69. (get-protocol-version session))))))
  70. (test-assert-with-log "authenticate-server, not-known"
  71. 'not-known
  72. (run-client-test
  73. ;; server
  74. simple-server-proc
  75. ;; client
  76. (lambda ()
  77. (call-with-connected-session
  78. (lambda (session)
  79. (authenticate-server session))))))
  80. (test-equal-with-log "authenticate-server, ok"
  81. 'ok
  82. (run-client-test
  83. ;; server
  84. simple-server-proc
  85. ;; client
  86. (lambda ()
  87. (let ((res (call-with-connected-session
  88. (lambda (session)
  89. (write-known-host! session)
  90. (authenticate-server session)))))
  91. (delete-file %knownhosts)
  92. res))))
  93. (test-assert-with-log "get-public-key-hash"
  94. (run-client-test
  95. ;; server
  96. simple-server-proc
  97. ;; client
  98. (lambda ()
  99. (let ((hash-md5-bv #vu8(15 142 110 203 162 228 250 211 20 212 26 217 118 57 217 66))
  100. (hash-md5-str "0f:8e:6e:cb:a2:e4:fa:d3:14:d4:1a:d9:76:39:d9:42")
  101. (hash-sha1-bv #vu8(20 65 56 155 119 45 84 163 50 26 59 92 215 159 139 5 229 174 84 80))
  102. (hash-sha1-str "14:41:38:9b:77:2d:54:a3:32:1a:3b:5c:d7:9f:8b:05:e5:ae:54:50")
  103. (session (make-session-for-test)))
  104. (sleep 1)
  105. (connect! session)
  106. (authenticate-server session)
  107. (let* ((pubkey (get-server-public-key session))
  108. (md5-res (get-public-key-hash pubkey 'md5))
  109. (sha1-res (get-public-key-hash pubkey 'sha1)))
  110. (disconnect! session)
  111. (and (bytevector=? md5-res hash-md5-bv)
  112. (string=? (bytevector->hex-string md5-res) hash-md5-str)
  113. (bytevector=? sha1-res hash-sha1-bv)
  114. (string=? (bytevector->hex-string sha1-res) hash-sha1-str)))))))
  115. ;;;
  116. ;;; Authentication
  117. ;;;
  118. ;;; 'userauth-none!'
  119. ;; The procedure called with a wrong object as a parameter which leads to an
  120. ;; exception.
  121. (test-error-with-log "userauth-none!, wrong parameter" 'wrong-type-arg
  122. (userauth-none! "Not a session."))
  123. ;; Client tries to authenticate using a non-connected session which leads to
  124. ;; an exception.
  125. (test-error-with-log "userauth-none!, not connected" 'wrong-type-arg
  126. (userauth-none! (make-session-for-test)))
  127. ;; Server replies with "success", client receives 'success.
  128. (test-equal-with-log "userauth-none!, success"
  129. 'success
  130. (run-client-test
  131. ;; server
  132. (lambda (server)
  133. (server-listen server)
  134. (let ((session (server-accept server)))
  135. (server-handle-key-exchange session)
  136. (start-session-loop session
  137. (lambda (msg type)
  138. (message-auth-set-methods! msg '(none))
  139. (message-reply-success msg)))))
  140. ;; client
  141. (lambda ()
  142. (call-with-connected-session
  143. (lambda (session)
  144. (authenticate-server session)
  145. (userauth-none! session))))))
  146. ;; Server replies with "default", client receives 'denied.
  147. (test-equal-with-log "userauth-none!, denied"
  148. 'denied
  149. (run-client-test
  150. ;; server
  151. (lambda (server)
  152. (server-listen server)
  153. (let ((session (server-accept server)))
  154. (server-handle-key-exchange session)
  155. (start-session-loop session
  156. (lambda (msg type)
  157. (message-auth-set-methods! msg '(public-key))
  158. (message-reply-default msg)))))
  159. ;; client
  160. (lambda ()
  161. (call-with-connected-session
  162. (lambda (session)
  163. (authenticate-server session)
  164. (userauth-none! session))))))
  165. ;; Server replies with "partial success", client receives 'partial.
  166. (test-equal-with-log "userauth-none!, partial"
  167. 'partial
  168. (run-client-test
  169. ;; server
  170. (lambda (server)
  171. (server-listen server)
  172. (let ((session (server-accept server)))
  173. (server-handle-key-exchange session)
  174. (start-session-loop session
  175. (lambda (msg type)
  176. (message-auth-set-methods! msg '(none))
  177. (message-reply-success msg 'partial)))))
  178. ;; client
  179. (lambda ()
  180. (call-with-connected-session
  181. (lambda (session)
  182. (authenticate-server session)
  183. (userauth-none! session))))))
  184. ;;; 'userauth-password!'
  185. ;; The procedure called with a wrong object as a parameter which leads to an
  186. ;; exception.
  187. (test-error-with-log "userauth-password!, session: non-session object"
  188. 'wrong-type-arg
  189. (userauth-password! "Not a session." "Password"))
  190. ;; Client tries to authenticate using a non-connected session which leads to
  191. ;; an exception.
  192. (test-error-with-log "userauth-password!, session: non-connected session"
  193. 'wrong-type-arg
  194. (userauth-password! (make-session-for-test) "Password"))
  195. ;; User tries to authenticate using a non-string object as a password. the
  196. ;; procedure raises an error.
  197. (test-error-with-log "userauth-password!, password: non-string object"
  198. 'wrong-type-arg
  199. (run-client-test
  200. ;; server
  201. (lambda (server)
  202. (server-listen server)
  203. (let ((session (server-accept server)))
  204. (server-handle-key-exchange session)
  205. (start-session-loop session
  206. (lambda (msg type)
  207. (message-auth-set-methods! msg '(password))
  208. (message-reply-success msg)))))
  209. ;; client
  210. (lambda ()
  211. (call-with-connected-session
  212. (lambda (session)
  213. (userauth-password! session 123))))))
  214. (test-equal-with-log "userauth-password!, success"
  215. 'success
  216. (run-client-test
  217. ;; server
  218. (lambda (server)
  219. (server-listen server)
  220. (let ((session (server-accept server)))
  221. (server-handle-key-exchange session)
  222. (start-session-loop session
  223. (lambda (msg type)
  224. (message-auth-set-methods! msg '(password))
  225. (message-reply-success msg)))))
  226. ;; client
  227. (lambda ()
  228. (call-with-connected-session
  229. (lambda (session)
  230. (authenticate-server session)
  231. (userauth-password! session "password"))))))
  232. (test-equal-with-log "userauth-password!, denied"
  233. 'denied
  234. (run-client-test
  235. ;; server
  236. (lambda (server)
  237. (server-listen server)
  238. (let ((session (server-accept server)))
  239. (server-handle-key-exchange session)
  240. (start-session-loop session
  241. (lambda (msg type)
  242. (message-auth-set-methods! msg '(password))
  243. (message-reply-default msg)))))
  244. ;; client
  245. (lambda ()
  246. (call-with-connected-session
  247. (lambda (session)
  248. (authenticate-server session)
  249. (userauth-password! session "password"))))))
  250. (test-equal-with-log "userauth-password!, partial"
  251. 'partial
  252. (run-client-test
  253. ;; server
  254. (lambda (server)
  255. (server-listen server)
  256. (let ((session (server-accept server)))
  257. (server-handle-key-exchange session)
  258. (start-session-loop session
  259. (lambda (msg type)
  260. (message-auth-set-methods! msg '(password))
  261. (message-reply-success msg 'partial)))))
  262. ;; client
  263. (lambda ()
  264. (call-with-connected-session
  265. (lambda (session)
  266. (authenticate-server session)
  267. (userauth-password! session "password"))))))
  268. ;;; 'userauth-public-key!'
  269. ;; The procedure called with a wrong object as a parameter which leads to an
  270. ;; exception.
  271. (test-error-with-log "userauth-public-key!, wrong parameter" 'wrong-type-arg
  272. (userauth-public-key! "Not a session." (private-key-from-file %rsakey)))
  273. ;; Client tries to authenticate using a non-connected session which leads to
  274. ;; an exception.
  275. (test-error-with-log "userauth-public-key!, non-connected session"
  276. 'wrong-type-arg
  277. (userauth-public-key! (make-session-for-test)
  278. (private-key-from-file %rsakey)))
  279. ;; Client tries to use a non-key object for authentication, the procedure
  280. ;; raises an exception.
  281. (test-error-with-log "userauth-public-key!, private-key: non-key object"
  282. 'wrong-type-arg
  283. (run-client-test
  284. ;; server
  285. (lambda (server)
  286. (server-listen server)
  287. (let ((session (server-accept server)))
  288. (server-handle-key-exchange session)
  289. (start-session-loop session
  290. (lambda (msg type)
  291. (message-reply-success msg)))))
  292. ;; client
  293. (lambda ()
  294. (call-with-connected-session
  295. (lambda (session)
  296. (userauth-public-key! session "Non-key object."))))))
  297. ;; Client tries to use a public key for authentication, the procedure raises
  298. ;; an exception.
  299. (test-error-with-log "userauth-public-key!, private-key: public key"
  300. 'wrong-type-arg
  301. (run-client-test
  302. ;; server
  303. (lambda (server)
  304. (server-listen server)
  305. (let ((session (server-accept server)))
  306. (server-handle-key-exchange session)
  307. (start-session-loop session
  308. (lambda (msg type)
  309. (message-reply-success msg)))))
  310. ;; client
  311. (lambda ()
  312. (call-with-connected-session
  313. (lambda (session)
  314. (userauth-public-key! session (public-key-from-file %rsakey-pub)))))))
  315. (test-equal-with-log "userauth-public-key!, success"
  316. 'success
  317. (run-client-test
  318. ;; server
  319. (lambda (server)
  320. (server-listen server)
  321. (let ((session (server-accept server)))
  322. (server-handle-key-exchange session)
  323. (start-session-loop session
  324. (lambda (msg type)
  325. (message-reply-success msg)))))
  326. ;; client
  327. (lambda ()
  328. (call-with-connected-session
  329. (lambda (session)
  330. (authenticate-server session)
  331. (let ((prvkey (private-key-from-file %rsakey)))
  332. (userauth-public-key! session prvkey)))))))
  333. ;;; 'userauth-public-key/auto!'
  334. ;; The procedure called with a wrong object as a parameter which leads to an
  335. ;; exception.
  336. (test-error-with-log "userauth-public-key/auto!, session: non-session object"
  337. 'wrong-type-arg
  338. (userauth-public-key/auto! "Not a session."))
  339. ;; Client tries to authenticate using a non-connected session which leads to
  340. ;; an exception.
  341. (test-error-with-log "userauth-public-key/auto!, session: non-connected session"
  342. 'wrong-type-arg
  343. (userauth-public-key/auto! (make-session-for-test)))
  344. ;;;
  345. ;; The procedure called with a wrong object as a parameter which leads to an
  346. ;; exception.
  347. (test-error-with-log "userauth-get-list, wrong parameter" 'wrong-type-arg
  348. (userauth-get-list "Not a session."))
  349. (test-error-with-log "userauth-get-list, non-connected" 'wrong-type-arg
  350. (userauth-get-list (make-session-for-test)))
  351. ;; Server replies "default" with the list of allowed authentication
  352. ;; methods. Client receives the list.
  353. (test-equal-with-log "userauth-get-list"
  354. '(password public-key)
  355. (run-client-test
  356. ;; server
  357. (lambda (server)
  358. (let ((session (server-accept server)))
  359. (server-handle-key-exchange session)
  360. (start-session-loop session
  361. (lambda (msg type)
  362. (message-auth-set-methods! msg '(password public-key))
  363. (message-reply-default msg)))))
  364. ;; client
  365. (lambda ()
  366. (call-with-connected-session
  367. (lambda (session)
  368. (authenticate-server session)
  369. (userauth-none! session)
  370. (userauth-get-list session))))))
  371. ;;; Channel test
  372. ;; make, open, exec
  373. (define (start-server/channel-test server)
  374. "Start SERVER for a channel test."
  375. (start-server-loop server
  376. (let ((channel #f))
  377. (lambda (msg)
  378. (let ((msg-type (message-get-type msg)))
  379. (srvmsg msg-type)
  380. (case (car msg-type)
  381. ((request-channel-open)
  382. (set! channel (message-channel-request-open-reply-accept msg)))
  383. ((request-channel)
  384. (if (equal? (cadr msg-type) 'channel-request-exec)
  385. (let ((cmd (exec-req:cmd (message-get-req msg))))
  386. (cond
  387. ((string=? cmd "ping")
  388. (write-line "pong" channel)
  389. (message-reply-success msg))
  390. ((string=? cmd "uname") ; For exit status testing
  391. (message-reply-success msg)
  392. (channel-request-send-exit-status channel 0))))
  393. (message-reply-success msg)))
  394. (else
  395. (message-reply-success msg))))))))
  396. (define (call-with-connected-session/channel-test proc)
  397. (define (loop count)
  398. (catch #t
  399. (lambda ()
  400. (call-with-connected-session
  401. (lambda (session)
  402. (authenticate-server session)
  403. (userauth-none! session)
  404. (proc session))))
  405. (lambda (key args)
  406. (format-log/scm 'nolog
  407. "make-session/channel-test"
  408. "Unable to connect in ~d tries: ~a~%"
  409. (- max-tries count)
  410. session)
  411. (sleep 1)
  412. (if (zero? count)
  413. (format-log/scm 'nolog
  414. "make-session/channel-test"
  415. "~a"
  416. "Giving up ...")
  417. (loop (1- count))))))
  418. (loop 30))
  419. (test-assert "make-channel"
  420. (run-client-test
  421. ;; server
  422. (lambda (server)
  423. (start-server/channel-test server))
  424. ;; client
  425. (lambda ()
  426. (call-with-connected-session/channel-test
  427. make-channel))))
  428. (test-assert-with-log "channel-get-session"
  429. (run-client-test
  430. ;; server
  431. (lambda (server)
  432. (start-server/channel-test server))
  433. ;; client
  434. (lambda ()
  435. (call-with-connected-session/channel-test
  436. (lambda (session)
  437. (let ((channel (make-channel session)))
  438. (eq? session (channel-get-session channel))))))))
  439. (test-assert-with-log "channel-open-session"
  440. (run-client-test
  441. ;; server
  442. (lambda (server)
  443. (start-server/channel-test server))
  444. ;; client
  445. (lambda ()
  446. (call-with-connected-session/channel-test
  447. (lambda (session)
  448. (let ((channel (make-channel session)))
  449. (channel-open-session channel)
  450. (not (port-closed? channel))))))))
  451. ;; Client sends "ping" as a command to execute, server replies with "pong"
  452. (test-assert-with-log "channel-request-exec"
  453. (run-client-test
  454. ;; server
  455. (lambda (server)
  456. (start-server/channel-test server))
  457. ;; client
  458. (lambda ()
  459. (call-with-connected-session/channel-test
  460. (lambda (session)
  461. (let ((channel (make-channel session)))
  462. (channel-open-session channel)
  463. (channel-request-exec channel "ping")
  464. (let ((res (read-line channel)))
  465. (and res
  466. (string=? "pong" res)))))))))
  467. ;; Client sends "uname" as a command to execute, server returns exit status 0.
  468. (test-assert-with-log "channel-request-exec, exit status"
  469. 0
  470. (run-client-test
  471. ;; server
  472. (lambda (server)
  473. (start-server/channel-test server))
  474. ;; client
  475. (lambda ()
  476. (call-with-connected-session/channel-test
  477. (lambda (session)
  478. (let ((channel (make-channel session)))
  479. (channel-open-session channel)
  480. (channel-request-exec channel "uname")
  481. (channel-get-exit-status channel)))))))
  482. ;; data transferring
  483. ;; FIXME: Probably these TCs can be implemented more elegantly.
  484. (define (make-channel/dt-test session)
  485. (let ((c (make-channel session)))
  486. (channel-open-session c)
  487. c))
  488. (test-assert-with-log "data transferring, string"
  489. (run-client-test
  490. ;; server
  491. (lambda (server)
  492. (start-server/dt-test server
  493. (lambda (channel)
  494. (let ((str (read-line channel)))
  495. (write-line str channel)))))
  496. ;; client
  497. (lambda ()
  498. (call-with-connected-session/channel-test
  499. (lambda (session)
  500. (let ((channel (make-channel/dt-test session))
  501. (str "Hello Scheme World!"))
  502. (write-line str channel)
  503. (poll channel
  504. (lambda args
  505. (let ((res (read-line channel)))
  506. (disconnect! session)
  507. (equal? res str))))))))))
  508. (test-assert-with-log "data transferring, bytevector"
  509. (let ((vect-size 10)
  510. (vect-fill 10))
  511. (run-client-test
  512. ;; server
  513. (lambda (server)
  514. (start-server/dt-test server
  515. (lambda (channel)
  516. (let ((v (get-bytevector-n channel vect-size)))
  517. (put-bytevector channel v)))))
  518. ;; client
  519. (lambda ()
  520. (call-with-connected-session/channel-test
  521. (lambda (session)
  522. (let ((channel (make-channel/dt-test session))
  523. (vect (make-bytevector vect-size vect-fill)))
  524. (put-bytevector channel vect)
  525. (poll channel
  526. (lambda args
  527. (let ((res (get-bytevector-n channel vect-size)))
  528. (equal? res vect)))))))))))
  529. ;;;
  530. ;;; Channels
  531. ;;;
  532. ;; Client opens a channel to a server, sends data and then sends EOF on the
  533. ;; channel. Server reads data and sends it back. Client checks if the
  534. ;; channel is closed for output, and reads the data.
  535. (test-assert-with-log "channel-send-eof"
  536. (run-client-test
  537. (lambda (server)
  538. (start-server/dt-test server
  539. (lambda (channel)
  540. (let ((str (read-line channel)))
  541. (write-line str channel)))))
  542. (lambda ()
  543. (call-with-connected-session/channel-test
  544. (lambda (session)
  545. (let ((channel (make-channel/dt-test session))
  546. (str "Hello Scheme World!"))
  547. (write-line str channel)
  548. (channel-send-eof channel)
  549. (and (input-port? channel)
  550. (not (output-port? channel))
  551. (string=? (read-line channel) str))))))))
  552. ;;;
  553. (test-end "client-server")
  554. (exit (= (test-runner-fail-count (test-runner-current)) 0))
  555. ;;; client-server.scm ends here.