;;; -*- Mode:LISP; Package:TELNET; Base:10; Readtable:CL -*- #| Copyright LISP Machine, Inc. 1985, 1986, 1987 See filename "Copyright.Text" for licensing and release information. The Lambda has 4 User Telnet programs: 1) System-T -- Telnet window 2) System-K -- Kermit H19 emulator 3) (telnet:telnet-glass-tty) -- this file. crude, displays option negotiations without action. 3) (telnet:telnet) -- this file. crude but fast. Ignores option negotiations. For now, this is at the level of a crude hack. There is no negotiation, no interpretation of TELNET_IAC. What it provides is enough to say: (telnet-glass-tty "100.0.0.10") As a quick but functional hack. Good for debugging the rest of the TCP implemenation. 3/30/85 16:22:32 -George Carrette |# (defmacro without-more-processing (the-window &body body) "execute body with more processing disabled" (let ((more-p (gentemp "MOREP")) (window (gentemp "WINDOW"))) `(let* ((,window ,the-window) (,more-p (send ,window :more-p))) (unwind-protect (progn (send ,window :set-more-p nil) ,@body) (send ,window ':set-more-p ,more-p))))) (eval-when (compile eval load) (defun glass-tty-ascii-code (lispm-char) (cond ((char-bit lispm-char :control) (logxor #o100 (char-upcase (glass-tty-ascii-code (char-code lispm-char))))) ((not (zerop (char-bits lispm-char))) nil) ((member lispm-char '(#\Return #\Line #\Tab #\Form) :test #'eq) (- lispm-char #o200)) ((= lispm-char #\Rubout) #o177) ((graphic-char-p lispm-char) (char-code lispm-char)))) (compiler:defoptimizer glass-tty-ascii-code-constant glass-tty-ascii-code (form) (global:destructuring-bind (nil arg) form (if (constantp arg) (eval form) form))) ) (defvar *telsyms* '( ( 255 iac "interpret as command:") ( 254 dont "you are not to use option") ( 253 do "please, you use option") ( 252 wont "I won't use option") ( 251 will "I will use option") ( 250 sb "interpret as subnegotiation") ( 249 ga "you may reverse the line") ( 248 el "erase the current line") ( 247 ec "erase the current character") ( 246 ayt "are you there") ( 245 ao "abort output--but let prog finish") ( 244 ip "interrupt process--permanently") ( 243 break "break") ( 242 dm "data mark--for connect. cleaning") ( 241 nop "nop") ( 240 se "end sub negotiation") ( 242 synch "for telfunc calls"))) (dolist (x *telsyms*) (proclaim `(special ,(cadr x))) (set (cadr x) (car x)) (setf (get (cadr x) 'telnet-sym) (car x))) (defvar *telopts* '( ( 0 telopt_binary "8-bit data path") ( 1 telopt_echo "echo") ( 2 telopt_rcp "prepare to reconnect") ( 3 telopt_sga "suppress go ahead") ( 4 telopt_nams "approximate message size") ( 5 telopt_status "give status") ( 6 telopt_tm "timing mark") ( 7 telopt_rcte "remote controlled transmission and echo") ( 8 telopt_naol "negotiate about output line width") ( 9 telopt_naop "negotiate about output page size") ( 10 telopt_naocrd "negotiate about CR disposition") ( 11 telopt_naohts "negotiate about horizontal tabstops") ( 12 telopt_naohtd "negotiate about horizontal tab disposition") ( 13 telopt_naoffd "negotiate about formfeed disposition") ( 14 telopt_naovts "negotiate about vertical tab stops") ( 15 telopt_naovtd "negotiate about vertical tab disposition") ( 16 telopt_naolfd "negotiate about output LF disposition") ( 17 telopt_xascii "extended ascii character set") ( 18 telopt_logout "force logout") ( 19 telopt_bm "byte macro") ( 20 telopt_det "data entry terminal") ( 21 telopt_supdup "supdup protocol") ( 22 telopt_supdup-output "supdup output allowed") ( 255 telopt_exopl "extended-options-list"))) (dolist (x *telopts*) (proclaim `(special ,(cadr x))) (set (cadr x) (car x)) (setf (get (cadr x) 'telnet-opt) (car x))) (defun telnet-glass-tty (address &optional (port "TELNET") half-duplex) "This glass TTY works well enough for testing purposes" (check-type address string) (without-more-processing *terminal-io* (with-open-file (stream (string-append "TCP-HOST:" address "." port) :keyword "Telnet Glass TTY" :auto-force-output (not half-duplex)) (catch 'eof (let (p) (unwind-protect (progn (setq p (process-run-function "telnet input from remote" #'telnet-glass-tty-characters-from-remote stream *terminal-io* sys:current-process)) (if half-duplex (do ((line)) (nil) (setq line (global:prompt-and-read :string "")) (with-input-from-string (s line) (telnet-glass-tty-handle-keyboard s stream) (send stream :tyo (glass-tty-ascii-code #\return)) (send stream :tyo (glass-tty-ascii-code #\line)) (send stream :force-output))) (telnet-glass-tty-handle-keyboard *terminal-io* stream))) (and p (send p :kill)))))))) (defun telnet-glass-tty-characters-from-remote (from to superior) (global:condition-case (x) (do ((c) (iac (get 'iac 'telnet-sym))) ((null (setq c (send from :tyi))) (format to "~&***CONNECTION CLOSED AT REMOTE END***~%")) (cond ((eq c iac) (glass-tty-telnet-iac from to)) ((eq c #o12)) ((member c '(#o10 #o11 #o14 #o15) ) ;test #eq (send to :tyo (+ c #o200))) ('else (send to :tyo c)))) (error (send x :report to))) (send superior :interrupt #'(lambda () (throw 'eof nil))) (si:process-wait-forever)) (defun telnet-glass-tty-handle-keyboard (from to) (do ((c)) ((null (setq c (send from :tyi)))) (cond ((= c #\return) (send to :tyo (glass-tty-ascii-code #\return)) (send to :tyo (glass-tty-ascii-code #\line))) ('else (let ((cm (glass-tty-ascii-code c))) (when cm (send to :tyo cm))))))) (defun glass-tty-telnet-iac (remote-stream terminal) (format terminal "~&***IAC ") (let ((cs (send remote-stream :tyi))) (case (cadr (assoc cs *telsyms* :test #'eq)) (nil (format terminal " undefined in protocol: ~D ~%" cs)) ((do dont will wont) (let ((oc (send remote-stream :tyi))) (format terminal "~A ~A~%" (caddr (assoc cs *telsyms* :test #'eq)) (caddr (assoc oc *telopts* :test #'eq))))) (sb (format terminal "~A~%" (caddr (assoc cs *telsyms* :test #'eq))) (do () ((eq (send remote-stream :tyi) (get 'se 'telnet-sym))))) (t (format terminal "~A~%" (caddr (assoc cs *telsyms* :test #'eq))))))) ;;;Here is a User Telnet that talks directly to a TCP socket. ;;It is fast, but primitive -- no option negotiation. (defvar *telnet-logging* nil) (defvar *telnet-data-log* nil) (defvar *telnet-final-stats-block* nil) (defvar *telnet-final-tcp-stats-block* nil) (defun initialize-data-log () (setq *telnet-data-log* nil)) (defun log-data (buffer) (when *telnet-logging* (push (string buffer) *telnet-data-log*))) (defparameter *telnet-receive-buffers* 8) (defparameter *telnet-receive-buffer-size* 256) (defun telnet (host) (if (ip-header-p host) (setq host (ip:ih-dest-address host)) (let ((original-host host)) (assert (numberp (setq host (parse-internet-address (setq original-host host)))) (host) "~S is not a valid Internet host specification" original-host))) (let ((socket (make-tcp-socket :keyword "User Telnet")) (opened nil) (open nil) (term-in-buffer nil) (term-out-buffer nil)) (without-more-processing *terminal-io* (initialize-data-log) (unwind-protect (cond ((setq opened (send socket :open :remote-port 23 :remote-address host :active t :auto-push t :optimistic t)) (setq term-in-buffer (make-array 256 :fill-pointer 0 :element-type '(unsigned-byte 8))) (setq term-out-buffer (make-string 256)) (loop (process-wait "Terminal or Network" #'(lambda (term) (or (send socket :listen) (and open (send term :listen)))) *terminal-io*) (cond ((send *terminal-io* :listen) (get-terminal-data term-in-buffer) (copy-data-to-net term-in-buffer socket)) ((send socket :listen) (let ((item (send socket :read-data))) (case (first item) (:open (dotimes (i *telnet-receive-buffers*) (send socket :receive (make-array *telnet-receive-buffer-size* :fill-pointer 0 :element-type '(unsigned-byte 8)))) (setq open t)) (:data ;Data from remote host (log-data (second item)) (copy-data-to-terminal (second item) term-out-buffer) (unless (eq (third item) :eof) (send socket :receive (second item)))) (:closing ;Remote side has closed (return "Closed by remote end")) (:reset (setq opened open) (return (if open "Connection reset" "Connection refused"))) (:close ;Socket closed out from under us (setq opened nil) (return "Closed locally")) ((:network-unreachable :host-unreachable :protocol-unreachable :port-unreachable) (return "Unreachable")) (:timeout (send socket :abort) (setq opened nil) (return "Timed out")) (otherwise ;;Ignore it ))))))) (t "TCP not running")) (when opened (send socket :close)) (setq *telnet-final-stats-block* (tcp:tcp-user-statistics-block socket)) (setq *telnet-final-tcp-stats-block* (tcp:tcp-user-stats socket)) (setq *telnet-data-log* (nreverse *telnet-data-log*)))))) (defun playback () (let ((term-out-buffer (make-string 256))) (dolist (x *telnet-data-log*) (copy-data-to-terminal x term-out-buffer)))) (defun get-terminal-data (buffer) (do ((limit (array-length buffer)) (index 0)) ((or (not (send *terminal-io* :listen)) (>= index limit)) (setf (fill-pointer buffer) index) buffer) (let ((char (char-to-ascii (send *terminal-io* :tyi)))) (when char ;;(send *terminal-io* :tyo char) (setf (aref buffer index) char) (incf index) (when (eq char #o15) (setf (aref buffer index) #o12) (incf index)))))) (defun copy-data-to-net (buffer socket) (when (plusp (length buffer)) (send socket :write-data (string buffer)))) (defun copy-data-to-terminal (in-buffer out-buffer) (do ((index 0 (1+ index)) (count 0) (limit (length in-buffer))) ((>= index limit) (when (plusp count) (send *terminal-io* :string-out out-buffer 0 count))) (let ((char (ascii-to-char (aref in-buffer index)))) (cond ((eq char #o210) (when (plusp count) (send *terminal-io* :string-out out-buffer 0 count) (setq count 0)) (send *terminal-io* :tyo char)) (char (setf (char out-buffer count) char) (incf count)))))) (defun char-to-ascii (lispm-char) (cond ((char-bit lispm-char :control) (logxor #o100 (char-upcase (char-to-ascii (char-code lispm-char))))) ((not (zerop (char-bits lispm-char))) nil) ((member lispm-char `(,(char-int #\return) ,(char-int #\line) ,(char-int #\tab) ,(char-int #\form))) (- lispm-char #o200)) ((= lispm-char (char-int #\Rubout)) #o177) ((graphic-char-p lispm-char) (char-code lispm-char)))) (defun ascii-to-char (ascii-char) (cond ((> ascii-char #o177) ;;high bit set nil) ((= ascii-char #o15) ;;Carriage Return -- ignore it nil) ((= ascii-char #o12) ;;Line Feed -- convert to RETURN #\return) ((member ascii-char '(#o11 #o14 #o10)) ;; Tab, Form Feed, Backspace (+ ascii-char #o200)) ((< ascii-char #o40) ;;Other control character nil) ('else ;;Normal printing character ascii-char)))