;;; -*- Mode:LISP; Package:TELNET; Readtable:CL; Base:10 -*- #| Copyright LISP Machine, Inc. 1985, 1987 See filename "Copyright.Text" for licensing and release information. This was a little 3k character file before I added :rubout-handler capabilities. Still basically a one-day-hack though. 5/13/85 10:59:22 -George Carrette Well, i've moved the terminal stuff to another file. |# (define-network-service *tcp-telnet-service* :telnet :tcp "Network ASCII Terminal Capability" :listen-port (sym tcp-application:ipport-telnet) :toplevel-function 'telnet-server-function :auto-enable? t) (defresource telnet-server (&optional ascii-output-stream ascii-input-stream) :constructor (make-instance 'telnet-server :output ascii-output-stream :input ascii-input-stream) :matcher (progn object) :initializer (progn (setf (global:symeval-in-instance object 'output) ascii-output-stream) (setf (global:symeval-in-instance object 'input) ascii-input-stream) (setf (global:symeval-in-instance object 'output-lock) nil) (send object :termcap :default)) :deinitializer (progn (setf (global:symeval-in-instance object 'output) nil) (setf (global:symeval-in-instance object 'input) nil) (setf (global:symeval-in-instance object 'telnet-options-received) nil) (setf (global:symeval-in-instance object 'telnet-options-sent) nil) (send object :termcap :default)) :initial-copies 1) (defvar network-user:*term* nil "The terminal type, as a keyword, eg. :H19") (defvar *network-user-process-bindings* '((*package* (find-package "NETWORK-USER")) (*print-base* 10.) (base 10.) (*read-base* 10.) (ibase 10.) (*error-output* (make-synonym-stream '*terminal-io*))) "These are used in addition to *BREAK-BINDINGS*") (defvar *telnet-interrupt-characters* `((,(glass-tty-ascii-code #\Control-g) network-user:handle-abort) (,(glass-tty-ascii-code #\Control-t) network-user:status-interrupt process-status-info) (,(glass-tty-ascii-code #\Control-z) network-user:handle-error-break))) (defvar *telnet-ascii-quote-character* (glass-tty-ascii-code #\Control-V)) (defvar *telnet-quote-character* #\Control-V) (defvar *telnet-ascii-stop-output-character* (glass-tty-ascii-code #\Control-S)) (defvar *telnet-ascii-resume-output-character* (glass-tty-ascii-code #\Control-Q)) (defvar *telnet-asynchronous-force-output-period* 60.) (defun telnet-server-function (remote-stream) (let* ((safe-input-stream (make-eof-throwing-stream remote-stream)) (si:user-id nil) (network-user:*term* nil) (cvars '(network-user:*term* si:user-id))) (global:using-resource (terminal telnet-server remote-stream safe-input-stream) (catch 'eof (send terminal :send-if-handles :send-initial-telnet-frobs) (format terminal "~%Welcome to ~A Server Telnet.~%" (send si:local-host :name)) (send terminal :force-output) (network-user-login terminal) (send terminal :initialize-terminal) (global:print-herald terminal) (format terminal "~%Type (help) for keyboard help~%~%") (send terminal :force-output) (subprocess :closure-variables cvars (loop (send terminal :force-output) (sys:process-sleep *telnet-asynchronous-force-output-period*))) (let ((buffer-stream (make-io-buffer-stream (global:symeval-in-instance terminal 'tv:io-buffer)))) (send terminal :set-input-stream buffer-stream) (send terminal :set-more-p t) (send sys:current-process :set-priority 1) (catch 'telnet-server-logout (telnet-server-input (subprocess :closure-variables cvars :restart-after-reset t (global:progw (append *network-user-process-bindings* si:*break-bindings*) (catch 'telnet-server-logout (si:lisp-top-level1 terminal)) (send (network-server-process *server*) :interrupt #'network-user:logout))) buffer-stream remote-stream terminal))))))) (defun telnet-server-input (user-process buffer-stream remote-stream terminal) (do (c int quote extended) ((null (setq c (send remote-stream :tyi)))) (setq extended (termcap.extended-keyboard (global:symeval-in-instance terminal 'termcap))) (cond ((= c (get 'iac 'telnet-sym)) (let* ((c1 (send remote-stream :tyi)) (action (cadr (assoc c1 *telsyms* :test #'eq)))) (cond ((eq action 'ip) (send user-process :interrupt 'network-user:handle-abort-all)) ((eq action 'ao) ;;Abort Output ) ((eq action 'ayt) (send user-process :interrupt 'network-user:status-interrupt (process-status-info user-process))) (t (send buffer-stream :tyo c) (send buffer-stream :tyo c1))))) ((and extended (= c #o34)) (let ((bits (send remote-stream :tyi))) (cond ((= bits #o034) (send buffer-stream :tyo c)) (t (setq c (make-char (global:char-flipcase (send remote-stream :tyi)) (logand bits #o77))) (case c (#\Control-Abort (send user-process :interrupt 'network-user:handle-abort)) (#\Control-Meta-Abort (send user-process :interrupt 'network-user:handle-abort-all)) (#\Control-Break (send user-process :interrupt 'network-user:handle-break)) (#\Control-Meta-Break (send user-process :interrupt 'network-user:handle-error-break)) (otherwise (send buffer-stream :tyo (char-int c)))))))) ((and (not quote) (= c *telnet-ascii-quote-character*)) (setq quote t)) ((and (not quote) (= c *telnet-ascii-stop-output-character*)) (unless (eq (global:symeval-in-instance terminal 'output-lock) global:current-process) (global:process-lock (locf (global:symeval-in-instance terminal 'output-lock))))) ((and (not quote) (= c *telnet-ascii-resume-output-character*)) (when (eq (global:symeval-in-instance terminal 'output-lock) global:current-process) (global:process-unlock (locf (global:symeval-in-instance terminal 'output-lock))))) ((and (not quote) (setq int (assoc c *telnet-interrupt-characters* :test #'eq))) (cond ((third int) (send user-process :interrupt (second int) (funcall (third int) user-process))) (t (send user-process :interrupt (second int))))) ((send buffer-stream :buffer-full-p) ;; GOOD QUESTION. LETS JUST THROW AWAY CHARACTERS, OTHERWISE ;; WE WILL MISS ANY #\CONTROL-G'S COMING DOWN. (send remote-stream :tyo (glass-tty-ascii-code #\Control-g))) (t (when quote (send buffer-stream :tyo *telnet-ascii-quote-character*) (setq quote nil)) (send buffer-stream :tyo c))))) (defun read-command-line (stream format &rest args) (let ((st (read-line stream t nil nil `((:prompt ,(apply #'format nil format args)))))) (cond ((null st) nil) ((zerop (length st)) nil) (t st)))) (defun read-command-line-unechoed (stream format &rest args) (apply #'format stream format args) (do ((char (send stream :tyi) (send stream :tyi)) (line (make-string 30 :fill-pointer 0))) ((null char) nil) (cond ((= char #\Rubout) (when (plusp (fill-pointer line)) (vector-pop line))) ((= char #\Clear-Input) (setf (fill-pointer line) 0)) ((= char #\Return) (fresh-line stream) (return (if (plusp (fill-pointer line)) line nil))) ((/= 0 (char-bits char)) (send stream :beep)) (t (vector-push-extend char line))))) (defvar *network-user-login-punt* 3) (defun network-user-login (terminal &aux user pass) (do ((j 1 (1+ j))) (nil) (setq user (read-command-line terminal "Username: ")) (setq pass (read-command-line-unechoed terminal "Password: ")) (if (validate-network-server-password user pass si:local-host) (return nil)) (format terminal "%ERROR: Invalid Username or Password~%") (when (and *network-user-login-punt* (>= j *network-user-login-punt*)) (format terminal "Autologout after ~D tries~%" j) (throw 'eof nil))) (setq si:user-id user) (unless (member (global:symeval-in-instance terminal 'term) '(:supdup :supdup-output)) (loop (setq network-user:*term* (read-command-line terminal "Terminal-type: ")) (and (null network-user:*term*) (return nil)) (setq network-user:*term* (intern (string-upcase network-user:*term*) "")) (and (get network-user:*term* 'termcap) (return (send terminal :termcap network-user:*term*))) (format terminal "~&Unknown terminal type: ~S (hit to punt)~%" network-user:*term*)))) (defun process-status-info (&optional (p sys:current-process)) (list :hostname (send si:local-host :short-name) :state (or (si:process-wait-whostate p) (si:process-run-whostate p)) :cpu-time (* (send p :cpu-time) 1.0e-6) :disk-wait-time (* (send p :disk-wait-time) 1.0e-6) :page-faults (si:process-page-fault-count p) :percent-utilization (send p :percent-utilization) :current-time (time:get-universal-time))) (define-network-service *tcp-supdup-service* :supdup :tcp "SUPDUP Terminal Capability" :listen-port (sym tcp-application:ipport-supdup) :toplevel-function 'supdup-server-function :auto-enable? t) (defresource supdup-server (&optional ascii-output-stream ascii-input-stream) :constructor (make-instance 'supdup-server :output ascii-output-stream :input ascii-input-stream) :matcher (progn object) :initializer (progn (setf (global:symeval-in-instance object 'output) ascii-output-stream) (setf (global:symeval-in-instance object 'input) ascii-input-stream) (setf (global:symeval-in-instance object 'output-lock) nil) (send object :termcap :default)) :deinitializer (progn (setf (global:symeval-in-instance object 'output) nil) (setf (global:symeval-in-instance object 'input) nil) (send object :termcap :default)) :initial-copies 1) (defun supdup-server-function (remote-stream) (let* ((safe-input-stream (make-eof-throwing-stream remote-stream)) (si:user-id nil) (network-user:*term* nil) (cvars '(network-user:*term* si:user-id))) (global:using-resource (terminal supdup-server remote-stream safe-input-stream) (catch 'eof (send terminal :supdup-greeting "Welcome to ~A SUPDUP Server." (send si:local-host :name)) (send terminal :initialize-terminal) (network-user-login terminal) (send terminal :terpri) (global:print-herald terminal) (format terminal "~%Type (help) for keyboard help~%~%") (send terminal :force-output) (subprocess :closure-variables cvars (loop (send terminal :force-output) (sys:process-sleep *telnet-asynchronous-force-output-period*))) (let ((buffer-stream (make-io-buffer-stream (global:symeval-in-instance terminal 'tv:io-buffer)))) (send terminal :set-input-stream buffer-stream) (send terminal :set-more-p t) (send sys:current-process :set-priority 1) (catch 'telnet-server-logout (supdup-server-input (subprocess :closure-variables cvars :restart-after-reset t (global:progw (append *network-user-process-bindings* si:*break-bindings*) (catch 'telnet-server-logout (si:lisp-top-level1 terminal)) (send (network-server-process *server*) :interrupt #'network-user:logout))) buffer-stream remote-stream terminal))))))) (defun supdup-server-input (user-process buffer-stream remote-stream terminal) (declare (ignore terminal)) (do (c bits code) ((null (setq c (send remote-stream :tyi)))) (when (= c #o34) (setq bits (send remote-stream :tyi)) (cond ((null bits) (setq c nil)) ((= bits #o34) (setq c (int-char c))) (t (setq bits (logand bits #o37)) (setq code (send remote-stream :tyi)) (cond ((null code) (setq c nil)) (t (setq c (dpb (logand bits #o20) (byte 5 7) (logand #o177 code))) (setq c (make-char (cond ((cadr (assoc c *ascii-supdup-translations* :test #'eq))) ((= bits #o20) code) (t (global:char-flipcase code))) (logand bits #o17)))))))) (cond ((null c) ;End Of File (send buffer-stream :tyo nil)) ((eq c #\Control-Abort) (send user-process :interrupt 'network-user:handle-abort)) ((eq c #\Control-Meta-Abort) (send user-process :interrupt 'network-user:handle-abort-all)) ((eq c #\Control-Break) (send user-process :interrupt 'network-user:handle-break)) ((eq c #\Control-Meta-Break) (send user-process :interrupt 'network-user:handle-error-break)) ((integerp c) ;Not prefixed by #o34 -- pass through (send buffer-stream :tyo c)) ((graphic-char-p c) ;Special Printing character -- pass through (send buffer-stream :tyo c)) (t (send buffer-stream :tyo (char-int c)))))) (add-initialization "SUPDUP" '(process-run-function "SUPDUP Server" 'chaos-supdup-server) NIL 'chaos:server-alist) (defun chaos-supdup-server () (let* ((conn (chaos:listen "SUPDUP")) (process-name (format nil "SUPDUP serving ~A" (chaos:host-short-name (chaos:foreign-address conn)))) (tcpa:*server* (tcpa:make-network-server :service (tcpa:make-network-service :name "SUPDUP") :process global:current-process :process-name process-name)) (stream nil)) (global:condition-case-if (not tcpa:*tcp-generic-server-toplevel-debug*) () (unwind-protect (progn (chaos:accept conn) (setq stream (chaos:make-stream conn)) (send tv:who-line-file-state-sheet :add-server conn "SUPDUP") (supdup-server-function stream)) (and stream (send stream :force-output)) (chaos:close-conn conn) (dolist (proc (tcpa:network-server-subprocesses *server*)) (send proc :kill)) (send tv:who-line-file-state-sheet :delete-server conn)) (error nil)))) (defun make-io-buffer-stream (buffer &aux stream) (declare (values stream buffer)) (setq stream #'(lambda (op &optional arg1 &rest args) (si:selectq-with-which-operations op (:tyi (tv:io-buffer-get buffer)) (:tyo (apply 'tv:io-buffer-put buffer arg1 args)) (:listen (not (tv:io-buffer-empty-p buffer))) (:clear-input (tv:io-buffer-clear buffer)) (:buffer-full-p (tv:io-buffer-full-p buffer)) (t ;; no other operations are expected but might as well ;; allow for them. (global:stream-default-handler stream op arg1 args))))) (values stream buffer)) (defun make-eof-throwing-stream (substream &aux stream) (setq stream #'(lambda (op &optional arg1 &rest args) (si:selectq-with-which-operations op (:tyo (send substream :tyo arg1)) (:tyi (or (send substream :tyi) (throw 'eof nil))) (:listen (send substream :listen)) (:force-output (send substream :force-output)) (:clear-output (send substream :clear-output)) (:clear-input (send substream :clear-input)) (t ;; no other operations are expected but might as well ;; allow for them. (global:stream-default-handler stream op arg1 args))))))