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
|
;;; tramp-gw.el --- Tramp utility functions for HTTP tunnels and SOCKS gateways
;; Copyright (C) 2007-2014 Free Software Foundation, Inc.
;; Author: Michael Albinus <michael.albinus@gmx.de>
;; Keywords: comm, processes
;; Package: tramp
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Access functions for HTTP tunnels and SOCKS gateways from Tramp.
;; SOCKS functionality is implemented by socks.el from the w3 package.
;; HTTP tunnels are partly implemented in socks.el and url-http.el;
;; both implementations are not complete. Therefore, it is
;; implemented in this package.
;;; Code:
(require 'tramp)
;; Pacify byte-compiler.
(eval-when-compile
(require 'cl)
(require 'custom))
(defvar socks-noproxy)
;; We don't add the following methods to `tramp-methods', in order to
;; exclude them from file name completion.
;; Define HTTP tunnel method ...
;;;###tramp-autoload
(defconst tramp-gw-tunnel-method "tunnel"
"Method to connect HTTP gateways.")
;; ... and port.
(defconst tramp-gw-default-tunnel-port 8080
"Default port for HTTP gateways.")
;; Define SOCKS method ...
;;;###tramp-autoload
(defconst tramp-gw-socks-method "socks"
"Method to connect SOCKS servers.")
;; ... and port.
(defconst tramp-gw-default-socks-port 1080
"Default port for SOCKS servers.")
;; Autoload the socks library. It is used only when we access a SOCKS server.
(autoload 'socks-open-network-stream "socks")
(defvar socks-username (user-login-name))
(defvar socks-server
(list "Default server" "socks" tramp-gw-default-socks-port 5))
;; Add a default for `tramp-default-user-alist'. Default is the local user.
;;;###tramp-autoload
(add-to-list
'tramp-default-user-alist
(list (concat "\\`"
(regexp-opt (list tramp-gw-tunnel-method tramp-gw-socks-method))
"\\'")
nil (user-login-name)))
;; Internal file name functions and variables.
(defvar tramp-gw-vector nil
"Keeps the remote host identification. Needed for Tramp messages.")
(defvar tramp-gw-gw-vector nil
"Current gateway identification vector.")
(defvar tramp-gw-gw-proc nil
"Current gateway process.")
;; This variable keeps the listening process, in order to reuse it for
;; new processes.
(defvar tramp-gw-aux-proc nil
"Process listening on local port, as mediation between SSH and the gateway.")
(defun tramp-gw-gw-proc-sentinel (proc _event)
"Delete auxiliary process when we are deleted."
(unless (memq (process-status proc) '(run open))
(tramp-message
tramp-gw-vector 4 "Deleting auxiliary process `%s'" tramp-gw-gw-proc)
(let* ((tramp-verbose 0)
(p (tramp-get-connection-property proc "process" nil)))
(when (processp p) (delete-process p)))))
(defun tramp-gw-aux-proc-sentinel (proc _event)
"Activate the different filters for involved gateway and auxiliary processes."
(when (memq (process-status proc) '(run open))
;; A new process has been spawned from `tramp-gw-aux-proc'.
(tramp-message
tramp-gw-vector 4
"Opening auxiliary process `%s', speaking with process `%s'"
proc tramp-gw-gw-proc)
(tramp-compat-set-process-query-on-exit-flag proc nil)
;; We don't want debug messages, because the corresponding debug
;; buffer might be undecided.
(let ((tramp-verbose 0))
(tramp-set-connection-property tramp-gw-gw-proc "process" proc)
(tramp-set-connection-property proc "process" tramp-gw-gw-proc))
;; Set the process-filter functions for both processes.
(set-process-filter proc 'tramp-gw-process-filter)
(set-process-filter tramp-gw-gw-proc 'tramp-gw-process-filter)
;; There might be already some output from the gateway process.
(with-current-buffer (process-buffer tramp-gw-gw-proc)
(unless (= (point-min) (point-max))
(let ((s (buffer-string)))
(delete-region (point) (point-max))
(tramp-gw-process-filter tramp-gw-gw-proc s))))))
(defun tramp-gw-process-filter (proc string)
(let ((tramp-verbose 0))
(process-send-string
(tramp-get-connection-property proc "process" nil) string)))
;;;###tramp-autoload
(defun tramp-gw-open-connection (vec gw-vec target-vec)
"Open a remote connection to VEC (see `tramp-file-name' structure).
Take GW-VEC as SOCKS or HTTP gateway, i.e. its method must be a
gateway method. TARGET-VEC identifies where to connect to via
the gateway, it can be different from VEC when there are more
hops to be applied.
It returns a string like \"localhost#port\", which must be used
instead of the host name declared in TARGET-VEC."
;; Remember vectors for property retrieval.
(setq tramp-gw-vector vec
tramp-gw-gw-vector gw-vec)
;; Start listening auxiliary process.
(unless (and (processp tramp-gw-aux-proc)
(memq (process-status tramp-gw-aux-proc) '(listen)))
(let ((aux-vec
(vector "aux" (tramp-file-name-user gw-vec)
(tramp-file-name-host gw-vec) nil nil)))
(setq tramp-gw-aux-proc
(make-network-process
:name (tramp-buffer-name aux-vec) :buffer nil :host 'local
:server t :noquery t :service t :coding 'binary))
(set-process-sentinel tramp-gw-aux-proc 'tramp-gw-aux-proc-sentinel)
(tramp-compat-set-process-query-on-exit-flag tramp-gw-aux-proc nil)
(tramp-message
vec 4 "Opening auxiliary process `%s', listening on port %d"
tramp-gw-aux-proc (process-contact tramp-gw-aux-proc :service))))
(let* ((gw-method
(intern
(tramp-find-method
(tramp-file-name-method gw-vec)
(tramp-file-name-user gw-vec)
(tramp-file-name-host gw-vec))))
(socks-username
(tramp-find-user
(tramp-file-name-method gw-vec)
(tramp-file-name-user gw-vec)
(tramp-file-name-host gw-vec)))
;; Declare the SOCKS server to be used.
(socks-server
(list "Tramp temporary socks server list"
;; Host name.
(tramp-file-name-real-host gw-vec)
;; Port number.
(or (tramp-file-name-port gw-vec)
(case gw-method
(tunnel tramp-gw-default-tunnel-port)
(socks tramp-gw-default-socks-port)))
;; Type. We support only http and socks5, NO socks4.
;; 'http could be used when HTTP tunnel works in socks.el.
5))
;; The function to be called.
(socks-function
(case gw-method
(tunnel 'tramp-gw-open-network-stream)
(socks 'socks-open-network-stream)))
socks-noproxy)
;; Open SOCKS process.
(setq tramp-gw-gw-proc
(funcall
socks-function
(tramp-get-connection-name gw-vec)
(tramp-get-connection-buffer gw-vec)
(tramp-file-name-real-host target-vec)
(tramp-file-name-port target-vec)))
(set-process-sentinel tramp-gw-gw-proc 'tramp-gw-gw-proc-sentinel)
(tramp-compat-set-process-query-on-exit-flag tramp-gw-gw-proc nil)
(tramp-message
vec 4 "Opened %s process `%s'"
(case gw-method ('tunnel "HTTP tunnel") ('socks "SOCKS"))
tramp-gw-gw-proc)
;; Return the new host for gateway access.
(format "localhost#%d" (process-contact tramp-gw-aux-proc :service))))
(defun tramp-gw-open-network-stream (name buffer host service)
"Open stream to proxy server HOST:SERVICE.
Resulting process has name NAME and buffer BUFFER. If
authentication is requested from proxy server, provide it."
(let ((command (format (concat
"CONNECT %s:%d HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Connection: keep-alive\r\n"
"User-Agent: Tramp/%s\r\n")
host service host service tramp-version))
(authentication "")
(first t)
found proc)
(while (not found)
;; Clean up.
(when (processp proc) (delete-process proc))
(with-current-buffer buffer (erase-buffer))
;; Open network stream.
(setq proc (open-network-stream
name buffer (nth 1 socks-server) (nth 2 socks-server)))
(set-process-coding-system proc 'binary 'binary)
(tramp-compat-set-process-query-on-exit-flag proc nil)
;; Send CONNECT command.
(process-send-string proc (format "%s%s\r\n" command authentication))
(tramp-message
tramp-gw-vector 6 "\n%s"
(format
"%s%s\r\n" command
(tramp-compat-replace-regexp-in-string ;; no password in trace!
"Basic [^\r\n]+" "Basic xxxxx" authentication t)))
(with-current-buffer buffer
;; Trap errors to be traced in the right trace buffer. Often,
;; proxies have a timeout of 60". We wait 65" in order to
;; receive an answer this case.
(ignore-errors
(let ((tramp-verbose 0))
(tramp-wait-for-regexp proc 65 "\r?\n\r?\n")))
;; Check return code.
(goto-char (point-min))
(narrow-to-region
(point-min)
(or (search-forward-regexp "\r?\n\r?\n" nil t) (point-max)))
(tramp-message tramp-gw-vector 6 "\n%s" (buffer-string))
(goto-char (point-min))
(search-forward-regexp "^HTTP/[1-9]\\.[0-9]" nil t)
(case (condition-case nil (read (current-buffer)) (error))
;; Connected.
(200 (setq found t))
;; We need basic authentication.
(401 (setq authentication (tramp-gw-basic-authentication nil first)))
;; Target host not found.
(404 (tramp-error-with-buffer
(current-buffer) tramp-gw-vector 'file-error
"Host %s not found." host))
;; We need basic proxy authentication.
(407 (setq authentication (tramp-gw-basic-authentication t first)))
;; Connection failed.
(503 (tramp-error-with-buffer
(current-buffer) tramp-gw-vector 'file-error
"Connection to %s:%d failed." host service))
;; That doesn't work at all.
(t (tramp-error-with-buffer
(current-buffer) tramp-gw-vector 'file-error
"Access to HTTP server %s:%d failed."
(nth 1 socks-server) (nth 2 socks-server))))
;; Remove HTTP headers.
(delete-region (point-min) (point-max))
(widen)
(setq first nil)))
;; Return the process.
proc))
(defun tramp-gw-basic-authentication (proxy pw-cache)
"Return authentication header for CONNECT, based on server request.
PROXY is an indication whether we need a Proxy-Authorization header
or an Authorization header. If PW-CACHE is non-nil, check for
password in password cache. This is done for the first try only."
;; `tramp-current-*' must be set for `tramp-read-passwd'.
(let ((tramp-current-method (tramp-file-name-method tramp-gw-gw-vector))
(tramp-current-user (tramp-file-name-user tramp-gw-gw-vector))
(tramp-current-host (tramp-file-name-host tramp-gw-gw-vector)))
(unless pw-cache (tramp-clear-passwd tramp-gw-gw-vector))
;; We are already in the right buffer.
(tramp-message
tramp-gw-vector 5 "%s required"
(if proxy "Proxy authentication" "Authentication"))
;; Search for request header. We accept only basic authentication.
(goto-char (point-min))
(search-forward-regexp
"^\\(Proxy\\|WWW\\)-Authenticate:\\s-*Basic\\s-+realm=")
;; Return authentication string.
(format
"%s: Basic %s\r\n"
(if proxy "Proxy-Authorization" "Authorization")
(base64-encode-string
(format
"%s:%s"
socks-username
(tramp-read-passwd
nil
(format
"Password for %s@[%s]: " socks-username (read (current-buffer)))))))))
(add-hook 'tramp-unload-hook
(lambda ()
(unload-feature 'tramp-gw 'force)))
(provide 'tramp-gw)
;;; TODO:
;; * Provide descriptive Commentary.
;; * Enable it for several gateway processes in parallel.
;;; tramp-gw.el ends here
|