Initial Commit
[packages] / xemacs-packages / os-utils / ftelnet.el
1 ;;; ftelnet.el --- remote login interface
2
3 ;; Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
4 ;; Copyright (C) 1995 Noah S. Friedman
5
6 ;; Author: Noah Friedman
7 ;; Maintainer: Noah Friedman <friedman@prep.ai.mit.edu>
8 ;; Status: Works in Emacs 19.27 and later.
9 ;; Keywords: unix, comm
10
11 ;; LCD Archive Entry:
12 ;; ftelnet|Noah Friedman|friedman@prep.ai.mit.edu|
13 ;; remote login interface|
14 ;; $Date: 1998-10-02 05:58:07 $|$Revision: 1.1 $|~/misc/ftelnet.el.gz|
15
16 ;; $Id: ftelnet.el,v 1.1 1998-10-02 05:58:07 steveb Exp $
17
18 ;; This program is free software; you can redistribute it and/or modify
19 ;; it under the terms of the GNU General Public License as published by
20 ;; the Free Software Foundation; either version 2, or (at your option)
21 ;; any later version.
22 ;;
23 ;; This program is distributed in the hope that it will be useful,
24 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
25 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26 ;; GNU General Public License for more details.
27 ;;
28 ;; You should have received a copy of the GNU General Public License
29 ;; along with this program; if not, write to: The Free Software Foundation,
30 ;; Inc.; 675 Massachusetts Avenue.; Cambridge, MA 02139, USA.
31
32 ;;; Commentary:
33
34 ;; This code originally derived from emacs 19.29 rlogin.el, and was then
35 ;; modified substantially for use with telnet.
36
37 ;; Support for remote logins using `telnet'.
38 ;; This program is layered on top of shell.el; the code here only accounts
39 ;; for the variations needed to handle a remote process, e.g. directory
40 ;; tracking and the sending of some special characters.
41
42 ;; If you wish for ftelnet mode to prompt you in the minibuffer for
43 ;; passwords when a password prompt appears, just enter m-x send-invisible
44 ;; and type in your line, or add `comint-watch-for-password-prompt' to
45 ;; `comint-output-filter-functions'.
46
47 ;;; Code:
48
49 (require 'comint)
50 (require 'shell)
51
52 (defvar ftelnet-program "telnet"
53   "*Name of program to invoke telnet")
54
55 (defvar ftelnet-explicit-args nil
56   "*List of arguments to pass to telnet on the command line.")
57
58 (defvar ftelnet-mode-hook nil
59   "*Hooks to run after setting current buffer to ftelnet-mode.")
60
61 (defvar ftelnet-process-connection-type nil
62   "*If non-`nil', use a pty for the local telnet process.
63 If `nil', use a pipe (if pipes are supported on the local system).
64
65 Generally it is better not to waste ptys on systems which have a static
66 number of them.  On the other hand, some implementations of `telnet' assume
67 a pty is being used, and errors will result from using a pipe instead.")
68
69 (defvar ftelnet-directory-tracking-mode 'local
70   "*Control whether and how to do directory tracking in a telnet buffer.
71
72 nil means don't do directory tracking.
73
74 t means do so using an ftp remote file name.
75
76 Any other value means do directory tracking using local file names.
77 This works only if the remote machine and the local one
78 share the same directories (through NFS).  This is the default.
79
80 This variable becomes local to a buffer when set in any fashion for it.
81
82 It is better to use the function of the same name to change the behavior of
83 directory tracking in a telnet session once it has begun, rather than
84 simply setting this variable, since the function does the necessary
85 re-synching of directories.")
86
87 (make-variable-buffer-local 'ftelnet-directory-tracking-mode)
88
89 (defvar ftelnet-host nil
90   "The name of the remote host.  This variable is buffer-local.
91 There is usually no need to set this yourself.
92 ")
93
94 (defvar ftelnet-remote-user nil
95   "The username used on the remote host.
96 This variable is buffer-local and defaults to your local user name.
97 There is usually no need to set this yourself.")
98
99 ;; Initialize telnet mode map.
100 ; (setq ftelnet-mode-map nil)
101 (defvar ftelnet-mode-map '())
102 (cond
103  ((null ftelnet-mode-map)
104   (setq ftelnet-mode-map (if (consp shell-mode-map)
105                             (cons 'keymap shell-mode-map)
106                           (copy-keymap shell-mode-map)))
107   (define-key ftelnet-mode-map "\C-c\C-c" 'ftelnet-send-interrupt)
108   (define-key ftelnet-mode-map "\C-c\C-d" 'ftelnet-send-eof)
109   (define-key ftelnet-mode-map "\C-c\C-z" 'ftelnet-send-suspend)
110   (define-key ftelnet-mode-map "\C-c\C-\\" 'ftelnet-send-quit)
111   (define-key ftelnet-mode-map "\C-c\C-]" 'ftelnet-send-escape)
112   (define-key ftelnet-mode-map "\C-d" 'ftelnet-delete-or-send-eof)
113   (define-key ftelnet-mode-map "\C-i" 'ftelnet-tab-or-complete)))
114
115 (defvar ftelnet-urgent-alist
116   '((escape      . "\C-]")
117     (erase       . "\C-?")
118     (flushoutput . "\C-o")
119     (interrupt   . "\C-c")
120     (kill        . "\C-u")
121     (quit        . "\C-\\")
122     (eof         . "\C-d")))
123
124 (defvar ftelnet-history nil
125   "*History ring for ftelnet input arguments.")
126
127 \f
128 ;; See comments near ftelnet-pop-to-buffer for an explanation.
129 ;;;###autoload (add-hook 'same-window-regexps "^\\*telnet-.*\\*\\(\\|<[0-9]+>\\)")
130
131 ;;;###autoload
132 (defun ftelnet (input-args &optional buffer)
133   "Open a network login connection to HOST via the `telnet' program.
134 Input is sent line-at-a-time to the remote connection.
135
136 Communication with the remote host is recorded in a buffer *telnet-HOST*
137 \(or *telnet-HOST:PORT* if using a nonstandard port number\).
138 If a prefix argument is given and the buffer *telnet-HOST* already exists,
139 a new buffer with a different connection will be made.
140
141 When called from a program, if the optional second argument is a string or
142 buffer, it names the buffer to use.
143
144 The variable `ftelnet-program' contains the name of the actual program to
145 run.  It can be a relative or absolute path.
146
147 The variable `ftelnet-explicit-args' is a list of arguments to give to the
148 telnet program when starting.  They are added after any arguments given in
149 INPUT-ARGS.
150
151 If the default value of `ftelnet-directory-tracking-mode' is t, then the
152 default directory in that buffer is set to a remote (FTP) file name to
153 access your home directory on the remote machine.  Occasionally this causes
154 an error, if you cannot access the home directory on that machine.  This
155 error is harmless as long as you don't try to use that default directory.
156
157 If `ftelnet-directory-tracking-mode' is neither t nor nil, then the default
158 directory is initially set up to your (local) home directory.
159 This is useful if the remote machine and your local machine
160 share the same files via NFS.  This is the default.
161
162 If you wish to change directory tracking styles during a session, use the
163 function `ftelnet-directory-tracking-mode' rather than simply setting the
164 variable."
165   (interactive (list
166                 (read-from-minibuffer "telnet host (and optional port): "
167                                       nil nil nil 'ftelnet-history)
168                 current-prefix-arg))
169   (let* ((process-connection-type ftelnet-process-connection-type)
170          (args (if ftelnet-explicit-args
171                    (append (ftelnet-parse-words input-args)
172                            ftelnet-explicit-args)
173                  (ftelnet-parse-words input-args)))
174          ;; skip args starting with `-'
175          (nonopt-args (let ((l args))
176                         (while (= ?- (aref (car l) 0))
177                           (setq l (cdr l)))
178                         l))
179          (host (car nonopt-args))
180          (port (car (cdr nonopt-args)))
181          (buffer-name (if port
182                           (format "*telnet-%s:%s*" host port)
183                         (format "*telnet-%s*" host)))
184          proc)
185
186     (cond ((null buffer))
187           ((stringp buffer)
188            (setq buffer-name buffer))
189           ((bufferp buffer)
190            (setq buffer-name (buffer-name buffer)))
191           ((numberp buffer)
192            (setq buffer-name (format "%s<%d>" buffer-name buffer)))
193           (t
194            (setq buffer-name (generate-new-buffer-name buffer-name))))
195
196     (setq buffer (get-buffer-create buffer-name))
197     (ftelnet-pop-to-buffer buffer-name)
198
199     (cond
200      ((comint-check-proc buffer-name))
201      (t
202       (comint-exec buffer buffer-name ftelnet-program nil args)
203       (setq proc (get-buffer-process buffer))
204       ;; Set process-mark to point-max in case there is text in the
205       ;; buffer from a previous exited process.
206       (set-marker (process-mark proc) (point-max))
207
208       ;; comint-output-filter-functions is treated like a hook: it is
209       ;; processed via run-hooks or run-hooks-with-args in later versions
210       ;; of emacs.
211       ;; comint-output-filter-functions should already have a
212       ;; permanent-local property, at least in emacs 19.27 or later.
213       (cond
214        ((fboundp 'make-local-hook)
215         (make-local-hook 'comint-output-filter-functions)
216         (add-hook 'comint-output-filter-functions 'ftelnet-carriage-filter
217                   nil t))
218        (t
219         (make-local-variable 'comint-output-filter-functions)
220         (add-hook 'comint-output-filter-functions 'ftelnet-carriage-filter)))
221
222       (ftelnet-mode)
223
224       ;; initial filter to get remote user name if connecting to a telnet
225       ;; login port.
226       (cond
227        ((or (null port)
228             (string= port "23"))
229         (add-hook 'comint-output-filter-functions
230                   'ftelnet-user-output-filter)))
231
232       (make-local-variable 'ftelnet-host)
233       (setq ftelnet-host host)
234       (make-local-variable 'ftelnet-remote-user)
235       (setq ftelnet-remote-user nil)
236
237       (cond
238        ((eq t ftelnet-directory-tracking-mode))
239        ((null ftelnet-directory-tracking-mode))
240        (t
241         (cd-absolute (concat comint-file-name-prefix "~/"))))))))
242
243 (defun ftelnet-mode ()
244   "Set major-mode for ftelnet sessions.
245 If `ftelnet-mode-hook' is set, run it."
246   (interactive)
247   (kill-all-local-variables)
248   (shell-mode)
249   (setq major-mode 'ftelnet-mode)
250   (setq mode-name "ftelnet")
251   (use-local-map ftelnet-mode-map)
252   (setq shell-dirtrackp ftelnet-directory-tracking-mode)
253   (make-local-variable 'comint-file-name-prefix)
254   (run-hooks 'ftelnet-mode-hook))
255
256 (defun ftelnet-directory-tracking-mode (&optional prefix)
257   "Do remote or local directory tracking, or disable entirely.
258
259 If called with no prefix argument or a unspecified prefix argument (just
260 ``\\[universal-argument]'' with no number) do remote directory tracking via
261 ange-ftp.  If called as a function, give it no argument.
262
263 If called with a negative prefix argument, disable directory tracking
264 entirely.
265
266 If called with a positive, numeric prefix argument, e.g.
267
268          \\[universal-argument] 1 \\[ftelnet-directory-tracking-mode]
269
270 then do directory tracking but assume the remote filesystem is the same as
271 the local system.  This only works in general if the remote machine and the
272 local one share the same directories (through NFS)."
273   (interactive "P")
274   (cond
275    ((or (null prefix)
276         (consp prefix))
277     (setq ftelnet-directory-tracking-mode t)
278     (setq shell-dirtrackp t)
279     (setq comint-file-name-prefix
280           (concat "/" ftelnet-remote-user "@" ftelnet-host ":")))
281    ((< prefix 0)
282     (setq ftelnet-directory-tracking-mode nil)
283     (setq shell-dirtrackp nil))
284    (t
285     (setq ftelnet-directory-tracking-mode 'local)
286     (setq comint-file-name-prefix "")
287     (setq shell-dirtrackp t)))
288   (cond
289    (shell-dirtrackp
290     (let* ((proc (get-buffer-process (current-buffer)))
291            (proc-mark (process-mark proc))
292            (current-input (buffer-substring proc-mark (point-max)))
293            (orig-point (point))
294            (offset (and (>= orig-point proc-mark)
295                         (- (point-max) orig-point))))
296       (unwind-protect
297           (progn
298             (delete-region proc-mark (point-max))
299             (goto-char (point-max))
300             (shell-resync-dirs))
301         (goto-char proc-mark)
302         (insert current-input)
303         (if offset
304             (goto-char (- (point-max) offset))
305           (goto-char orig-point)))))))
306
307 ;; Parse a line into its constituent parts (words separated by
308 ;; whitespace).  Return a list of the words.
309 (defun ftelnet-parse-words (line)
310   (let ((list nil)
311         (posn 0)
312         (match-data (match-data)))
313     (while (string-match "[^ \t\n]+" line posn)
314       (setq list (cons (substring line (match-beginning 0) (match-end 0))
315                        list))
316       (setq posn (match-end 0)))
317     (store-match-data (match-data))
318     (nreverse list)))
319
320 ;; Starting in Emacs 19.29, the variable same-window-regexps modifies how
321 ;; pop-to-buffer works; in particular, if the name of the buffer being
322 ;; switched to matches one of the regexps in same-window-regexps (which may
323 ;; be buffer-local to the current buffer), then pop-to-buffer acts like
324 ;; switch-to-buffer.  This gives users more control.
325 ;; This also explains the related autoload cookie near the top of the file.
326 (defun ftelnet-pop-to-buffer (buffer)
327   (if (boundp 'same-window-regexps)
328       (pop-to-buffer buffer)
329     (switch-to-buffer buffer)))
330
331 \f
332 ;; This should go on comint-output-filter-functions initially.
333 ;; Once it detects that a username has been prompted for, it adds an input
334 ;; filter that saves the username.
335 (defun ftelnet-user-output-filter (s)
336   (let ((data (match-data)))
337     (cond
338      ;; I fail to see how or why a process filter would get invoked with
339      ;; output consisting of the empty string, but it happens.
340      ((string= s ""))
341      ((string-match "\\(ogin: \\)\\|\\(sername: \\)$" s)
342       (add-hook 'comint-input-filter-functions 'ftelnet-user-input-filter))
343      ((string-match "^[\C-m\n]+$" s))
344      ((string-match "assword:[ \t]*$" s))
345      ((string-match ".*\\(incorrect\\)\\|\\(authorization failure\\)$" s))
346      ((null ftelnet-remote-user))
347      ((and ftelnet-remote-user
348            (string-match (concat ftelnet-remote-user "[\C-m\n]*$") s)))
349      (t
350       (remove-hook 'comint-output-filter-functions 'ftelnet-user-output-filter)
351       (remove-hook 'comint-input-filter-functions 'ftelnet-user-input-filter)
352       (cond
353        ((eq ftelnet-directory-tracking-mode t)
354         (cd-absolute comint-file-name-prefix)))))
355     (store-match-data data)))
356
357 (defun ftelnet-user-input-filter (s)
358   (remove-hook 'comint-input-filter-functions 'ftelnet-user-input-filter)
359   (setq ftelnet-remote-user (car (ftelnet-parse-words s)))
360   (cond
361    ((eq ftelnet-directory-tracking-mode t)
362     (setq comint-file-name-prefix
363           (concat "/" ftelnet-remote-user "@" ftelnet-host ":")))))
364
365 (defun ftelnet-carriage-filter (string)
366   (let* ((point-marker (point-marker))
367          (end (process-mark (get-buffer-process (current-buffer))))
368          (beg (or (and (boundp 'comint-last-output-start)
369                        comint-last-output-start)
370                   (- end (length string)))))
371     (goto-char beg)
372     (while (search-forward "\C-m" end t)
373       (delete-char -1))
374     (goto-char point-marker)))
375
376 \f
377 ;; Definitions for keybindings
378
379 (defun ftelnet-send-urgently (urgsym)
380   (let ((s (cdr (assq urgsym ftelnet-urgent-alist))))
381     (and s (comint-send-string nil s))))
382
383 (defun ftelnet-send-escape ()
384   (interactive)
385   (ftelnet-send-urgently 'escape))
386
387 (defun ftelnet-send-interrupt ()
388   (interactive)
389   (ftelnet-send-urgently 'interrupt))
390
391 (defun ftelnet-send-eof ()
392   (interactive)
393   (ftelnet-send-urgently 'eof))
394
395 (defun ftelnet-send-quit ()
396   (interactive)
397   (ftelnet-send-urgently 'quit))
398
399 (defun ftelnet-send-suspend ()
400   (interactive)
401   (comint-send-string nil "\C-z"))
402
403 (defun ftelnet-delete-or-send-eof (arg)
404   "\
405 Delete ARG characters forward, or send an eof to process if at end of buffer."
406   (interactive "p")
407   (if (eobp)
408       (ftelnet-send-eof)
409     (delete-char arg)))
410
411 (defun ftelnet-tab-or-complete ()
412   "Complete file name if doing directory tracking, or just insert TAB."
413   (interactive)
414   (if ftelnet-directory-tracking-mode
415       (comint-dynamic-complete)
416     (insert "\C-i")))
417
418 \f
419 (provide 'ftelnet)
420
421 ;; local variables:
422 ;; vc-make-backup-files: t
423 ;; end:
424
425 ;;; ftelnet.el ends here