Commit | Line | Data |
---|---|---|
00d6fd04 MA |
1 | ;;; tramp-gw.el --- Tramp utility functions for HTTP tunnels and SOCKS gateways |
2 | ||
ba318903 | 3 | ;; Copyright (C) 2007-2014 Free Software Foundation, Inc. |
00d6fd04 MA |
4 | |
5 | ;; Author: Michael Albinus <michael.albinus@gmx.de> | |
6 | ;; Keywords: comm, processes | |
bd78fa1d | 7 | ;; Package: tramp |
00d6fd04 MA |
8 | |
9 | ;; This file is part of GNU Emacs. | |
10 | ||
874a927a | 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
00d6fd04 | 12 | ;; it under the terms of the GNU General Public License as published by |
874a927a GM |
13 | ;; the Free Software Foundation, either version 3 of the License, or |
14 | ;; (at your option) any later version. | |
00d6fd04 MA |
15 | |
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
874a927a | 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
00d6fd04 MA |
23 | |
24 | ;;; Commentary: | |
25 | ||
26 | ;; Access functions for HTTP tunnels and SOCKS gateways from Tramp. | |
27 | ;; SOCKS functionality is implemented by socks.el from the w3 package. | |
28 | ;; HTTP tunnels are partly implemented in socks.el and url-http.el; | |
29 | ;; both implementations are not complete. Therefore, it is | |
30 | ;; implemented in this package. | |
31 | ||
32 | ;;; Code: | |
33 | ||
34 | (require 'tramp) | |
35 | ||
b74f0d96 | 36 | ;; Pacify byte-compiler. |
00d6fd04 MA |
37 | (eval-when-compile |
38 | (require 'cl) | |
f95527c8 MA |
39 | (require 'custom)) |
40 | (defvar socks-noproxy) | |
00d6fd04 | 41 | |
66feec8b MA |
42 | ;; We don't add the following methods to `tramp-methods', in order to |
43 | ;; exclude them from file name completion. | |
44 | ||
00d6fd04 | 45 | ;; Define HTTP tunnel method ... |
0f34aa77 MA |
46 | ;;;###tramp-autoload |
47 | (defconst tramp-gw-tunnel-method "tunnel" | |
fb7ada5f | 48 | "Method to connect HTTP gateways.") |
00d6fd04 MA |
49 | |
50 | ;; ... and port. | |
0f34aa77 | 51 | (defconst tramp-gw-default-tunnel-port 8080 |
fb7ada5f | 52 | "Default port for HTTP gateways.") |
00d6fd04 MA |
53 | |
54 | ;; Define SOCKS method ... | |
0f34aa77 MA |
55 | ;;;###tramp-autoload |
56 | (defconst tramp-gw-socks-method "socks" | |
fb7ada5f | 57 | "Method to connect SOCKS servers.") |
00d6fd04 MA |
58 | |
59 | ;; ... and port. | |
0f34aa77 | 60 | (defconst tramp-gw-default-socks-port 1080 |
fb7ada5f | 61 | "Default port for SOCKS servers.") |
00d6fd04 | 62 | |
0f34aa77 MA |
63 | ;; Autoload the socks library. It is used only when we access a SOCKS server. |
64 | (autoload 'socks-open-network-stream "socks") | |
65 | (defvar socks-username (user-login-name)) | |
66 | (defvar socks-server | |
67 | (list "Default server" "socks" tramp-gw-default-socks-port 5)) | |
68 | ||
00d6fd04 | 69 | ;; Add a default for `tramp-default-user-alist'. Default is the local user. |
b191c9d9 | 70 | ;;;###tramp-autoload |
66feec8b MA |
71 | (add-to-list |
72 | 'tramp-default-user-alist | |
73 | (list (concat "\\`" | |
74 | (regexp-opt (list tramp-gw-tunnel-method tramp-gw-socks-method)) | |
75 | "\\'") | |
76 | nil (user-login-name))) | |
00d6fd04 MA |
77 | |
78 | ;; Internal file name functions and variables. | |
79 | ||
80 | (defvar tramp-gw-vector nil | |
81 | "Keeps the remote host identification. Needed for Tramp messages.") | |
82 | ||
83 | (defvar tramp-gw-gw-vector nil | |
84 | "Current gateway identification vector.") | |
85 | ||
86 | (defvar tramp-gw-gw-proc nil | |
87 | "Current gateway process.") | |
88 | ||
89 | ;; This variable keeps the listening process, in order to reuse it for | |
90 | ;; new processes. | |
91 | (defvar tramp-gw-aux-proc nil | |
92 | "Process listening on local port, as mediation between SSH and the gateway.") | |
93 | ||
5d89d9d2 | 94 | (defun tramp-gw-gw-proc-sentinel (proc _event) |
00d6fd04 MA |
95 | "Delete auxiliary process when we are deleted." |
96 | (unless (memq (process-status proc) '(run open)) | |
97 | (tramp-message | |
98 | tramp-gw-vector 4 "Deleting auxiliary process `%s'" tramp-gw-gw-proc) | |
4c1f03ef | 99 | (let* ((tramp-verbose 0) |
00d6fd04 MA |
100 | (p (tramp-get-connection-property proc "process" nil))) |
101 | (when (processp p) (delete-process p))))) | |
102 | ||
5d89d9d2 | 103 | (defun tramp-gw-aux-proc-sentinel (proc _event) |
00d6fd04 MA |
104 | "Activate the different filters for involved gateway and auxiliary processes." |
105 | (when (memq (process-status proc) '(run open)) | |
106 | ;; A new process has been spawned from `tramp-gw-aux-proc'. | |
107 | (tramp-message | |
108 | tramp-gw-vector 4 | |
109 | "Opening auxiliary process `%s', speaking with process `%s'" | |
110 | proc tramp-gw-gw-proc) | |
bd8fadca | 111 | (tramp-compat-set-process-query-on-exit-flag proc nil) |
00d6fd04 MA |
112 | ;; We don't want debug messages, because the corresponding debug |
113 | ;; buffer might be undecided. | |
4c1f03ef | 114 | (let ((tramp-verbose 0)) |
00d6fd04 MA |
115 | (tramp-set-connection-property tramp-gw-gw-proc "process" proc) |
116 | (tramp-set-connection-property proc "process" tramp-gw-gw-proc)) | |
117 | ;; Set the process-filter functions for both processes. | |
118 | (set-process-filter proc 'tramp-gw-process-filter) | |
119 | (set-process-filter tramp-gw-gw-proc 'tramp-gw-process-filter) | |
120 | ;; There might be already some output from the gateway process. | |
121 | (with-current-buffer (process-buffer tramp-gw-gw-proc) | |
122 | (unless (= (point-min) (point-max)) | |
123 | (let ((s (buffer-string))) | |
124 | (delete-region (point) (point-max)) | |
125 | (tramp-gw-process-filter tramp-gw-gw-proc s)))))) | |
126 | ||
127 | (defun tramp-gw-process-filter (proc string) | |
4c1f03ef | 128 | (let ((tramp-verbose 0)) |
00d6fd04 MA |
129 | (process-send-string |
130 | (tramp-get-connection-property proc "process" nil) string))) | |
131 | ||
0f34aa77 | 132 | ;;;###tramp-autoload |
00d6fd04 MA |
133 | (defun tramp-gw-open-connection (vec gw-vec target-vec) |
134 | "Open a remote connection to VEC (see `tramp-file-name' structure). | |
135 | Take GW-VEC as SOCKS or HTTP gateway, i.e. its method must be a | |
136 | gateway method. TARGET-VEC identifies where to connect to via | |
137 | the gateway, it can be different from VEC when there are more | |
138 | hops to be applied. | |
139 | ||
140 | It returns a string like \"localhost#port\", which must be used | |
141 | instead of the host name declared in TARGET-VEC." | |
142 | ||
143 | ;; Remember vectors for property retrieval. | |
144 | (setq tramp-gw-vector vec | |
145 | tramp-gw-gw-vector gw-vec) | |
146 | ||
147 | ;; Start listening auxiliary process. | |
148 | (unless (and (processp tramp-gw-aux-proc) | |
149 | (memq (process-status tramp-gw-aux-proc) '(listen))) | |
150 | (let ((aux-vec | |
151 | (vector "aux" (tramp-file-name-user gw-vec) | |
2fe4b125 | 152 | (tramp-file-name-host gw-vec) nil nil))) |
00d6fd04 MA |
153 | (setq tramp-gw-aux-proc |
154 | (make-network-process | |
155 | :name (tramp-buffer-name aux-vec) :buffer nil :host 'local | |
156 | :server t :noquery t :service t :coding 'binary)) | |
157 | (set-process-sentinel tramp-gw-aux-proc 'tramp-gw-aux-proc-sentinel) | |
bd8fadca | 158 | (tramp-compat-set-process-query-on-exit-flag tramp-gw-aux-proc nil) |
00d6fd04 MA |
159 | (tramp-message |
160 | vec 4 "Opening auxiliary process `%s', listening on port %d" | |
161 | tramp-gw-aux-proc (process-contact tramp-gw-aux-proc :service)))) | |
162 | ||
163 | (let* ((gw-method | |
164 | (intern | |
165 | (tramp-find-method | |
166 | (tramp-file-name-method gw-vec) | |
167 | (tramp-file-name-user gw-vec) | |
168 | (tramp-file-name-host gw-vec)))) | |
169 | (socks-username | |
170 | (tramp-find-user | |
171 | (tramp-file-name-method gw-vec) | |
172 | (tramp-file-name-user gw-vec) | |
173 | (tramp-file-name-host gw-vec))) | |
174 | ;; Declare the SOCKS server to be used. | |
175 | (socks-server | |
40ba43b4 | 176 | (list "Tramp temporary socks server list" |
00d6fd04 MA |
177 | ;; Host name. |
178 | (tramp-file-name-real-host gw-vec) | |
179 | ;; Port number. | |
180 | (or (tramp-file-name-port gw-vec) | |
181 | (case gw-method | |
182 | (tunnel tramp-gw-default-tunnel-port) | |
183 | (socks tramp-gw-default-socks-port))) | |
184 | ;; Type. We support only http and socks5, NO socks4. | |
185 | ;; 'http could be used when HTTP tunnel works in socks.el. | |
186 | 5)) | |
187 | ;; The function to be called. | |
188 | (socks-function | |
189 | (case gw-method | |
190 | (tunnel 'tramp-gw-open-network-stream) | |
191 | (socks 'socks-open-network-stream))) | |
192 | socks-noproxy) | |
193 | ||
194 | ;; Open SOCKS process. | |
195 | (setq tramp-gw-gw-proc | |
196 | (funcall | |
197 | socks-function | |
66feec8b MA |
198 | (tramp-get-connection-name gw-vec) |
199 | (tramp-get-connection-buffer gw-vec) | |
00d6fd04 MA |
200 | (tramp-file-name-real-host target-vec) |
201 | (tramp-file-name-port target-vec))) | |
202 | (set-process-sentinel tramp-gw-gw-proc 'tramp-gw-gw-proc-sentinel) | |
bd8fadca | 203 | (tramp-compat-set-process-query-on-exit-flag tramp-gw-gw-proc nil) |
00d6fd04 MA |
204 | (tramp-message |
205 | vec 4 "Opened %s process `%s'" | |
206 | (case gw-method ('tunnel "HTTP tunnel") ('socks "SOCKS")) | |
207 | tramp-gw-gw-proc) | |
208 | ||
209 | ;; Return the new host for gateway access. | |
210 | (format "localhost#%d" (process-contact tramp-gw-aux-proc :service)))) | |
211 | ||
212 | (defun tramp-gw-open-network-stream (name buffer host service) | |
213 | "Open stream to proxy server HOST:SERVICE. | |
214 | Resulting process has name NAME and buffer BUFFER. If | |
215 | authentication is requested from proxy server, provide it." | |
216 | (let ((command (format (concat | |
217 | "CONNECT %s:%d HTTP/1.1\r\n" | |
218 | "Host: %s:%d\r\n" | |
219 | "Connection: keep-alive\r\n" | |
220 | "User-Agent: Tramp/%s\r\n") | |
221 | host service host service tramp-version)) | |
222 | (authentication "") | |
223 | (first t) | |
224 | found proc) | |
225 | ||
226 | (while (not found) | |
227 | ;; Clean up. | |
228 | (when (processp proc) (delete-process proc)) | |
229 | (with-current-buffer buffer (erase-buffer)) | |
230 | ;; Open network stream. | |
231 | (setq proc (open-network-stream | |
232 | name buffer (nth 1 socks-server) (nth 2 socks-server))) | |
233 | (set-process-coding-system proc 'binary 'binary) | |
bd8fadca | 234 | (tramp-compat-set-process-query-on-exit-flag proc nil) |
00d6fd04 MA |
235 | ;; Send CONNECT command. |
236 | (process-send-string proc (format "%s%s\r\n" command authentication)) | |
237 | (tramp-message | |
238 | tramp-gw-vector 6 "\n%s" | |
239 | (format | |
240 | "%s%s\r\n" command | |
af9ff9e8 | 241 | (tramp-compat-replace-regexp-in-string ;; no password in trace! |
00d6fd04 MA |
242 | "Basic [^\r\n]+" "Basic xxxxx" authentication t))) |
243 | (with-current-buffer buffer | |
244 | ;; Trap errors to be traced in the right trace buffer. Often, | |
245 | ;; proxies have a timeout of 60". We wait 65" in order to | |
246 | ;; receive an answer this case. | |
03c1ad43 | 247 | (ignore-errors |
4c1f03ef | 248 | (let ((tramp-verbose 0)) |
03c1ad43 | 249 | (tramp-wait-for-regexp proc 65 "\r?\n\r?\n"))) |
00d6fd04 MA |
250 | ;; Check return code. |
251 | (goto-char (point-min)) | |
252 | (narrow-to-region | |
253 | (point-min) | |
254 | (or (search-forward-regexp "\r?\n\r?\n" nil t) (point-max))) | |
255 | (tramp-message tramp-gw-vector 6 "\n%s" (buffer-string)) | |
256 | (goto-char (point-min)) | |
257 | (search-forward-regexp "^HTTP/[1-9]\\.[0-9]" nil t) | |
258 | (case (condition-case nil (read (current-buffer)) (error)) | |
259 | ;; Connected. | |
260 | (200 (setq found t)) | |
261 | ;; We need basic authentication. | |
262 | (401 (setq authentication (tramp-gw-basic-authentication nil first))) | |
263 | ;; Target host not found. | |
264 | (404 (tramp-error-with-buffer | |
265 | (current-buffer) tramp-gw-vector 'file-error | |
266 | "Host %s not found." host)) | |
267 | ;; We need basic proxy authentication. | |
268 | (407 (setq authentication (tramp-gw-basic-authentication t first))) | |
269 | ;; Connection failed. | |
270 | (503 (tramp-error-with-buffer | |
271 | (current-buffer) tramp-gw-vector 'file-error | |
272 | "Connection to %s:%d failed." host service)) | |
273 | ;; That doesn't work at all. | |
274 | (t (tramp-error-with-buffer | |
275 | (current-buffer) tramp-gw-vector 'file-error | |
276 | "Access to HTTP server %s:%d failed." | |
277 | (nth 1 socks-server) (nth 2 socks-server)))) | |
278 | ;; Remove HTTP headers. | |
279 | (delete-region (point-min) (point-max)) | |
280 | (widen) | |
281 | (setq first nil))) | |
282 | ;; Return the process. | |
283 | proc)) | |
284 | ||
285 | (defun tramp-gw-basic-authentication (proxy pw-cache) | |
286 | "Return authentication header for CONNECT, based on server request. | |
287 | PROXY is an indication whether we need a Proxy-Authorization header | |
288 | or an Authorization header. If PW-CACHE is non-nil, check for | |
289 | password in password cache. This is done for the first try only." | |
290 | ||
9c13938d | 291 | ;; `tramp-current-*' must be set for `tramp-read-passwd'. |
00d6fd04 MA |
292 | (let ((tramp-current-method (tramp-file-name-method tramp-gw-gw-vector)) |
293 | (tramp-current-user (tramp-file-name-user tramp-gw-gw-vector)) | |
294 | (tramp-current-host (tramp-file-name-host tramp-gw-gw-vector))) | |
9c13938d | 295 | (unless pw-cache (tramp-clear-passwd tramp-gw-gw-vector)) |
00d6fd04 MA |
296 | ;; We are already in the right buffer. |
297 | (tramp-message | |
298 | tramp-gw-vector 5 "%s required" | |
299 | (if proxy "Proxy authentication" "Authentication")) | |
300 | ;; Search for request header. We accept only basic authentication. | |
301 | (goto-char (point-min)) | |
302 | (search-forward-regexp | |
303 | "^\\(Proxy\\|WWW\\)-Authenticate:\\s-*Basic\\s-+realm=") | |
304 | ;; Return authentication string. | |
305 | (format | |
306 | "%s: Basic %s\r\n" | |
307 | (if proxy "Proxy-Authorization" "Authorization") | |
308 | (base64-encode-string | |
309 | (format | |
310 | "%s:%s" | |
311 | socks-username | |
312 | (tramp-read-passwd | |
9ce8462a | 313 | nil |
00d6fd04 MA |
314 | (format |
315 | "Password for %s@[%s]: " socks-username (read (current-buffer))))))))) | |
316 | ||
0f34aa77 MA |
317 | (add-hook 'tramp-unload-hook |
318 | (lambda () | |
319 | (unload-feature 'tramp-gw 'force))) | |
00d6fd04 MA |
320 | |
321 | (provide 'tramp-gw) | |
322 | ||
323 | ;;; TODO: | |
324 | ||
325 | ;; * Provide descriptive Commentary. | |
326 | ;; * Enable it for several gateway processes in parallel. | |
327 | ||
00d6fd04 | 328 | ;;; tramp-gw.el ends here |