client-server.scm 22 KB

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