-
Notifications
You must be signed in to change notification settings - Fork 0
/
telnet.scm
executable file
·491 lines (457 loc) · 20.4 KB
/
telnet.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
; ------ telnet protocol constants -------
;; Telnet protocol defaults
(define +TELNET-PORT+ 23)
;; Telnet protocol characters (don't change)
(define +IAC+ (ascii->char 255)) ;; "Interpret As Command"
(define +DONT+ (ascii->char 254))
(define +DO+ (ascii->char 253))
(define +WONT+ (ascii->char 252))
(define +WILL+ (ascii->char 251))
(define +theNULL+ (ascii->char 0))
(define +SE+ (ascii->char 240)) ;; Subnegotiation End
(define +NOP+ (ascii->char 241)) ;; No Operation
(define +DM+ (ascii->char 242)) ;; Data Mark
(define +BRK+ (ascii->char 243)) ;; Break
(define +IP+ (ascii->char 244)) ;; Interrupt process
(define +AO+ (ascii->char 245)) ;; Abort output
(define +AYT+ (ascii->char 246)) ;; Are You There
(define +EC+ (ascii->char 247)) ;; Erase Character
(define +EL+ (ascii->char 248)) ;; Erase Line
(define +GA+ (ascii->char 249)) ;; Go Ahead
(define +SB+ (ascii->char 250)) ;; Subnegotiation Begin
;; Telnet protocol options code (don't change)
;; These ones all come from arpa/telnet.h
(define +BINARY+ (ascii->char 0)) ;; 8-bit data path
(define +ECHO+ (ascii->char 1)) ;; echo
(define +RCP+ (ascii->char 2)) ;; prepare to reconnect
(define +SGA+ (ascii->char 3)) ;; suppress go ahead
(define +NAMS+ (ascii->char 4)) ;; approximate message size
(define +STATUS+ (ascii->char 5)) ;; give status
(define +TM+ (ascii->char 6)) ;; timing mark
(define +RCTE+ (ascii->char 7)) ;; remote controlled transmission and echo
(define +NAOL+ (ascii->char 8)) ;; negotiate about output line width
(define +NAOP+ (ascii->char 9)) ;; negotiate about output page size
(define +NAOCRD+ (ascii->char 10)) ;; negotiate about CR disposition
(define +NAOHTS+ (ascii->char 11)) ;; negotiate about horizontal tabstops
(define +NAOHTD+ (ascii->char 12)) ;; negotiate about horizontal tab disposition
(define +NAOFFD+ (ascii->char 13)) ;; negotiate about formfeed disposition
(define +NAOVTS+ (ascii->char 14)) ;; negotiate about vertical tab stops
(define +NAOVTD+ (ascii->char 15)) ;; negotiate about vertical tab disposition
(define +NAOLFD+ (ascii->char 16)) ;; negotiate about output LF disposition
(define +XASCII+ (ascii->char 17)) ;; extended ascii character set
(define +LOGOUT+ (ascii->char 18)) ;; force logout
(define +BM+ (ascii->char 19)) ;; byte macro
(define +DET+ (ascii->char 20)) ;; data entry terminal
(define +SUPDUP+ (ascii->char 21)) ;; supdup protocol
(define +SUPDUPOUTPUT+ (ascii->char 22)) ;; supdup output
(define +SNDLOC+ (ascii->char 23)) ;; send location
(define +TTYPE+ (ascii->char 24)) ;; terminal type
(define +EOR+ (ascii->char 25)) ;; end or record
(define +TUID+ (ascii->char 26)) ;; TACACS user identification
(define +OUTMRK+ (ascii->char 27)) ;; output marking
(define +TTYLOC+ (ascii->char 28)) ;; terminal location number
(define +VT3270REGIME+ (ascii->char 29)) ;; 3270 regime
(define +X3PAD+ (ascii->char 30)) ;; X.3 PAD
(define +NAWS+ (ascii->char 31)) ;; window size
(define +TSPEED+ (ascii->char 32)) ;; terminal speed
(define +LFLOW+ (ascii->char 33)) ;; remote flow control
(define +LINEMODE+ (ascii->char 34)) ;; Linemode option
(define +XDISPLOC+ (ascii->char 35)) ;; X Display Location
(define +OLD_ENVIRON+ (ascii->char 36)) ;; Old - Environment variables
(define +AUTHENTICATION+ (ascii->char 37)) ;; Authenticate
(define +ENCRYPT+ (ascii->char 38)) ;; Encryption option
(define +NEW_ENVIRON+ (ascii->char 39)) ;; New - Environment variables
;; the following ones come from
;; http://www.iana.org/assignments/telnet-options
;; Unfortunately, that document does not assign identifiers
;; to all of them, so we are making them up
(define +TN3270E+ (ascii->char 40)) ;; TN3270E
(define +XAUTH+ (ascii->char 41)) ;; XAUTH
(define +CHARSET+ (ascii->char 42)) ;; CHARSET
(define +RSP+ (ascii->char 43)) ;; Telnet Remote Serial Port
(define +COM_PORT_OPTION+ (ascii->char 44)) ;; Com Port Control Option
(define +SUPPRESS_LOCAL_ECHO+ (ascii->char 45)) ;; Telnet Suppress Local Echo
(define +TLS+ (ascii->char 46)) ;; Telnet Start TLS
(define +KERMIT+ (ascii->char 47)) ;; KERMIT
(define +SEND_URL+ (ascii->char 48)) ;; SEND-URL
(define +FORWARD_X+ (ascii->char 49)) ;; FORWARD_X
(define +PRAGMA_LOGON+ (ascii->char 138)) ;; TELOPT PRAGMA LOGON
(define +SSPI_LOGON+ (ascii->char 139)) ;; TELOPT SSPI LOGON
(define +PRAGMA_HEARTBEAT+ (ascii->char 140)) ;; TELOPT PRAGMA HEARTBEAT
(define +EXOPL+ (ascii->char 255)) ;; Extended-Options-List
(define +NOOPT+ (ascii->char 0))
;----------------------------------------------------------------
;other constants
(define +no-data+ 1)
(define +old-data+ 2)
(define +new-data+ 3)
(define +timeout+ 4)
(define +eof+ 5)
(define +ok+ 6)
(define +CR+ (ascii->char 13)) ;carriage return character
;----------------------------------------------------------------
(define log (get-logger "telnet"))
;telnet implementation
(define-record telnet
host
port
sock
(cookedq nil)
(eof #f)
(iacseq nil)
(sb 0)
(sbdataq nil)
(option-callback default-option-callback%)
(sb-option-callback default-sb-option-callback%)
(char-callback (lambda (c s)
(log +debug+ "char call back, ~d~%" (char->ascii c))))
(remove-return-char #t))
;given a host and (optional)port, initiates a connection
;and returns the telnet record
(define (open-telnet-session host . port)
(let* ((port (if (null? port) +TELNET-PORT+ (first port)))
(sock (socket-connect protocol-family/internet socket-type/stream host port)))
(make-telnet host port sock)))
(define (close-telnet-session tn)
(close-socket (telnet:sock tn))
(log +debug+ "~%Telnet Stream Closed~%"))
(define (default-option-callback% out-stream cmd code)
(log +debug+ "default option callback called~%")
(if (and (char=? cmd +DO+) (char=? code +TTYPE+))
(begin
(format out-stream "~a~a~a" +IAC+ +WILL+ +TTYPE+)
(log +info+ "send back: WILL!~%")
nil)
(let ((cc nil) (ok #f))
(cond ((or (char=? cmd +WILL+)
(char=? cmd +WONT+))
(set! cc +DONT+)
(log +info+ "DONT")
(set! ok #t))
((or (char=? cmd +DO+)
(char=? cmd +DONT+))
(set! cc +WONT+)
(log +info+ "WONT")
(set! ok #t)))
(if ok
(begin
(format out-stream "~a~a~a" +IAC+ cc code)
(log +info+ "Send back!~%")))
(log +info+ "IAC ~a not recognized" (char->ascii cmd)))))
(define (send-sub-terminal-type-is% s-out . ttype)
(let ((ttype (if (null? ttype) "UNKNOWN" ttype)))
(format s-out "~a~a~a~a~a~a~a" +IAC+ +SB+ +TTYPE+ (ascii->char 0) ttype +IAC+ +SE+)
(log +info+ "~%(~a)(~a)(~a)(~a)~a(~a)(~a)"
(char->ascii +IAC+) (char->ascii +SB+) (char->ascii +TTYPE+) 0 ttype (char->ascii +IAC+) (char->ascii +SE+))))
(define (default-sb-option-callback% out-stream sbdata)
(log +debug+ "sb-option-callback called~%")
(if (and (char=? (list-ref sbdata 0) +TTYPE+)
(char=? (list-ref sbdata 1) (ascii->char 1)))
(send-sub-terminal-type-is% out-stream)))
; reads one char from the input port, if data
; is not available then returns nil
(define (read-char-no-hang in)
(if (char-ready? in)
(read-char in)
nil))
; Internal procedure to read all the available data from the
; socket to cookedq. By default its a non-blocking call.
; Return : +no-data+, when no data filled the cookedq.
; Return : +old-data+, when no new data read from the socket but cookedq has
; old data.
; Return : +new-data+, when new data are read from socket stream to cookedq.
(with-record-fields
(sock char-callback option-callback sb-option-callback sb
remove-return-char cookedq eof sbdataq iacseq)
telnet tn
(define (process-sock-stream% tn . block-read)
(let ((sock-stream-in (socket:inport sock))
(sock-stream-out (socket:outport sock))
(block-read (if (null? block-read) #f (first block-read)))
(c nil) (cmd nil) (opt nil) (len (length cookedq)))
(unless eof
(if block-read
(set! c (read-char sock-stream-in))
(set! c (read-char-no-hang sock-stream-in)))
(if (eof-object? c)
(set-eof! tn #t)))
(if (or eof (null? c))
(if (= 0 len) +no-data+ +old-data+)
(let loop ()
(log +debug+ "process-sock-stream%: Got inside loop, c:~d~%"
(char->ascii c))
(case (length iacseq)
((0) ;;length of iacseq
(cond
((char=? c +theNULL+))
((char=? c (ascii->char 21)))
((char=? c +IAC+)
(set-iacseq! ;TODO; use destructive append
tn (append iacseq (list c))))
(else
(if (= sb 0)
(unless (and remove-return-char (char=? c +CR+))
(when char-callback
(char-callback c sock-stream-out))
(set-cookedq! tn (append cookedq (list c)))) ;TODO; use destructive append
(set-sbdataq! tn (append sbdataq (list c))))))) ;TODO; use destructive append
((1) ;;length of iacseq
(if (find c (list +DO+ +DONT+ +WILL+ +WONT+))
(set-iacseq! tn (append iacseq (list c))) ;TODO; use destructive append
(begin ;;else
(set-iacseq! tn nil)
(cond
((char=? c +IAC+) ;;+IAC+ +IAC+
(if (= 0 sb)
(set-cookedq! tn (append cookedq (list c))) ;TODO; use destructive append
(set-sbdataq! tn (append sbdataq (list c))))) ;TODO; use destructive append
((char=? c +SB+) ;;+IAC+ +SB+
(log +info+ "~%.......SB......~%")
(set-sb! tn 1)
(set-sbdataq! tn nil))
((char=? c +SE+) ;;+IAC+ +SE+
(set-sb! tn 0)
(if (/= 0 (length sbdataq))
(log +debug+ "sbdata: ~a" sbdataq))
(if sb-option-callback ;;TODO: change here
(sb-option-callback sock-stream-out sbdataq))
(log +debug+ "~%....SE........~%"))
(else
(if option-callback
(option-callback sock-stream-out c +NOOPT+)
(log +debug+ "IAC ~d not recognized" (char->ascii c))))))))
((2) ;;length of iacseq
(set! cmd (list-ref iacseq 1))
(set-iacseq! tn nil)
(set! opt c)
(cond
((or (char=? cmd +DO+)
(char=? cmd +DONT+))
(log +debug+ "IAC ~s ~d~%"
(if (char=? cmd +DO+)
"DO" "DONT")
(char->ascii opt))
(if option-callback
(option-callback sock-stream-out cmd opt)
(begin
(format sock-stream-out "~a~a~a" +IAC+ +WONT+ opt))))
((or (char=? cmd +WILL+)
(char=? cmd +WONT+))
(log +debug+ "IAC ~s ~d~%" (if (char=? cmd +WILL+) "WILL" "WONT")
(char->ascii opt))
(if option-callback
(option-callback sock-stream-out cmd opt)
(format sock-stream-out "~a~a~a" +IAC+ +DONT+ opt))))))
(set! c (read-char-no-hang sock-stream-in))
(if (eof-object? c)
(set-eof! tn #t)
(if (null? c)
(if (/= len (length cookedq))
+new-data+
(if (= 0 len)
(begin
+no-data+)
+old-data+))
(loop))))))))
(define (peek-available-data tn . block-read)
(let ((block-read (if (null? block-read) #f (first block-read))))
(process-sock-stream% tn block-read)
(list->string (telnet:cookedq tn))))
(define (read-available-data tn . block-read)
(let ((block-read (if (null? block-read) #f (first block-read)))
(result nil))
(process-sock-stream% tn block-read)
(set! result (list->string (telnet:cookedq tn)))
(set-telnet:cookedq tn nil)
result))
; searches for occurance of string str1 in str2
; Param: search-start - index where to start the search from in str2
; Param: case-sensitive - should do a case sensitive or insensitive
; match, by default it is true.
; Return: index of first occurance of str1 or returns -1
(define (search str1 str2 search-start . case-sensitive)
(let* ((case-sensitive (if (null? case-sensitive) #t
(first case-sensitive)))
(result
(if case-sensitive
(string-contains str2 str1 search-start)
(string-contains-ci str2 str1 search-start))))
(or result -1)))
; Read the cookedq from 0 to end-pos(excluding the character AT end-pos
(define (read-cookedq% tn end-pos)
(if (<= end-pos 0) nil
(let ((result (telnet:cookedq tn)))
(let loop ((i 0) (partial-cookedq result))
(if (< i (- end-pos 1))
(loop (+ i 1) (cdr partial-cookedq))
(begin
(set-telnet:cookedq tn (cdr partial-cookedq))
(set-cdr! partial-cookedq nil))))
(list->string result))))
; waits until in-port is ready to be read or timeout is
; reached.
; This considers eof as ready to be read
; Returns:
; '(), if timeout is reached.
; '(in-port), if the port is ready to be read or eof has been reached.
; Note: timeout is in seconds.
(define (wait-until-readable% in-port . timeout)
(let ((timeout (if (null? timeout) #f (first timeout))))
(select-ports timeout in-port)))
; Read until a given string is encountered.
; When no match is found, return nil with a +eof+ or +timeout+ .
; By default the timeout is 600 secs
; Returns (values string-read/nil +ok+/+eof+/+timeout+)
; Note: timeout is in seconds.
(with-record-fields
(sock cookedq eof)
telnet tn
(define (read-until tn str case-sensitive . timeout)
(let ((sock-stream-in (socket:inport sock))
(pos nil)
(block-read (if (null? timeout) #t #f))
(timeout (if (null? timeout) 600 (first timeout)))
(read-status (process-sock-stream% tn))
(data-len nil)
(search-start 0)
(start-time nil)
(elasped-time nil)
(str-len (string-length str)))
(when timeout (set! start-time (time)))
(let outer-loop ()
(set! data-len (length cookedq))
(if (>= (- data-len search-start) str-len)
(begin
(set! pos (search str (list->string cookedq) search-start case-sensitive))
(set! search-start (+ 2 (- data-len (+ 1 str-len)))))
(set! pos -1))
(cond
((>= pos 0)
(set! pos (+ pos str-len))
(values (read-cookedq% tn pos) +ok+))
(eof
(log +info+ "eof!!")
(values nil +eof+))
(else
(let inner-loop ()
(when timeout (set! elasped-time (- (time) start-time)))
(if (and timeout (>= elasped-time timeout))
(begin (log +info+ "TimeOut!") (values nil +timeout+))
(case (wait-until-readable% sock-stream-in (- timeout elasped-time))
((nil) (begin (log +info+ "TimeOut!") (values nil +timeout+)))
(else
(process-sleep .25) ;sleep for 250 ms
(set! read-status (process-sock-stream% tn block-read))
(cond
((= read-status +new-data+) (outer-loop))
(eof (log +info+ "eof!!")
(values nil +eof+))
(else (inner-loop)))))))))))))
; Read until one from a list of given strings is encountered.
; When no match is found, return nil with a +eof+ or +timeout+ .
; By default the timeout is 600 secs
; Returns (values string-read/nil +ok+/+eof+/+timeout+ -1/index-of-match-in-strings)
; Note: timeout is in seconds.
(with-record-fields
(sock cookedq eof)
telnet tn
(define (read-until-2 tn strings case-sensitive . timeout)
(if (= (length strings) 1)
(read-until tn (first strings) case-sensitive timeout)
(let ((sock-stream-in (socket:inport sock))
(pos -1)
(block-read (if (null? timeout) #t #f))
(timeout (if (null? timeout) 600 (first timeout)))
(read-status (process-sock-stream% tn))
(data-len nil)
(start-time nil)
(work-list
(map (lambda (str)
(list 0 (string-length str) str)) strings))
(match-ind nil) ;index in strings that matched
(elasped-time nil))
(when timeout (set! start-time (time)))
(let outer-loop ()
(set! data-len (length cookedq))
(set! match-ind -1)
(let work-list-loop ((wl work-list))
(set! match-ind (+ 1 match-ind))
(when (not (null? wl))
(when (>= (- data-len (first (car wl))) (second (car wl)))
(begin
(set! pos (search (third (car wl)) (list->string cookedq) (first (car wl)) case-sensitive))
(if (< pos 0)
(begin
(set-car! (car wl) (+ 2 (- data-len (+ 1 (second (car wl))))))
(work-list-loop (cdr wl)))
(set! pos (+ pos (second (car wl)))))))))
(cond
((>= pos 0)
(values (read-cookedq% tn pos) +ok+ match-ind))
(eof
(log +info+ "eof!!")
(values nil +eof+ -1))
(else
(let inner-loop ()
(when timeout (set! elasped-time (- (time) start-time)))
(if (and timeout (>= elasped-time timeout))
(begin (log +info+ "TimeOut!") (values nil +timeout+ -1))
(case (wait-until-readable% sock-stream-in (- timeout elasped-time))
((nil) (begin (log +info+ "TimeOut!") (values nil +timeout+ -1)))
(else
(process-sleep .25) ;sleep for 250 ms
(set! read-status (process-sock-stream% tn block-read))
(cond
((= read-status +new-data+) (outer-loop))
(eof (log +info+ "eof!!")
(values nil +eof+ -1))
(else (inner-loop))))))))))))))
; Read until a match to given regex is found.
; When no match is found, return nil with a +eof+ or +timeout+ .
; By default the timeout is 600 secs
; Returns (values string-read/nil +ok+/+eof+/+timeout+)
; Note: timeout is in seconds.
(with-record-fields
(sock cookedq eof)
telnet tn
(define (expect tn regex case-sensitive . timeout)
(let ((sock-stream-in (socket:inport sock))
(pos nil)
(block-read (if (null? timeout) #t #f))
(timeout (if (null? timeout) 600 (first timeout)))
(read-status (process-sock-stream% tn))
(match nil)
(start-time nil)
(elasped-time nil))
(when timeout (set! start-time (time)))
(let outer-loop ()
(set! match (regexp-search regex (list->string cookedq)))
(if match
(set! pos (match:end match))
(set! pos -1))
(cond
((>= pos 0)
(values (read-cookedq% tn pos) +ok+))
(eof
(log +info+ "eof!!")
(values nil +eof+))
(else
(let inner-loop ()
(when timeout (set! elasped-time (- (time) start-time)))
(if (and timeout (>= elasped-time timeout))
(begin (log +info+ "TimeOut!") (values nil +timeout+))
(case (wait-until-readable% sock-stream-in (- timeout elasped-time))
((nil) (begin (log +info+ "TimeOut!") (values nil +timeout+)))
(else
(process-sleep .25) ;sleep for 250 ms
(set! read-status (process-sock-stream% tn block-read))
(cond
((= read-status +new-data+) (outer-loop))
(eof (log +info+ "eof!!")
(values nil +eof+))
(else (inner-loop)))))))))))))
(define (write-ln tn str)
(format (socket:outport (telnet:sock tn)) "~a~%" str))
(define (write-ln-crlr tn str)
(format (socket:outport (telnet:sock tn))
"~a~a~a" str +CR+ #\newline))