diff options
| author | Michael Albinus | 2007-06-04 20:24:09 +0000 |
|---|---|---|
| committer | Michael Albinus | 2007-06-04 20:24:09 +0000 |
| commit | c50982cc9ca816c51984969978e39f0c047b3e27 (patch) | |
| tree | 7b262ce18e8d3855605b9ea7636a3b4413640766 | |
| parent | bc0c5eb8aab101d215537d3f5d6c9e6ca6459d51 (diff) | |
| download | emacs-c50982cc9ca816c51984969978e39f0c047b3e27.tar.gz emacs-c50982cc9ca816c51984969978e39f0c047b3e27.zip | |
* net/socks.el: New file, taken from w3 repository.
(top): Update Copyright. Don't load cl.el.
(all): Replace `case' by `cond', `string-to-int' by
`string-to-number', and `process-kill-without-query' by
`set-process-query-on-exit-flag'.
(socks-char-int): Remove defalias and all occurencies.
| -rw-r--r-- | lisp/ChangeLog | 9 | ||||
| -rw-r--r-- | lisp/net/socks.el | 650 |
2 files changed, 659 insertions, 0 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 9784429922b..0520747bb7d 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,12 @@ | |||
| 1 | 2007-06-04 Michael Albinus <michael.albinus@gmx.de> | ||
| 2 | |||
| 3 | * net/socks.el: New file, taken from w3 repository. | ||
| 4 | (top): Update Copyright. Don't load cl.el. | ||
| 5 | (all): Replace `case' by `cond', `string-to-int' by | ||
| 6 | `string-to-number', and `process-kill-without-query' by | ||
| 7 | `set-process-query-on-exit-flag'. | ||
| 8 | (socks-char-int): Remove defalias and all occurencies. | ||
| 9 | |||
| 1 | 2007-06-04 Juanma Barranquero <lekktu@gmail.com> | 10 | 2007-06-04 Juanma Barranquero <lekktu@gmail.com> |
| 2 | 11 | ||
| 3 | * progmodes/compile.el (compilation-find-file, compilation-handle-exit): | 12 | * progmodes/compile.el (compilation-find-file, compilation-handle-exit): |
diff --git a/lisp/net/socks.el b/lisp/net/socks.el new file mode 100644 index 00000000000..8290d0592f8 --- /dev/null +++ b/lisp/net/socks.el | |||
| @@ -0,0 +1,650 @@ | |||
| 1 | ;;; socks.el --- A Socks v5 Client for Emacs | ||
| 2 | |||
| 3 | ;; Copyright (C) 1996 - 1998 by William M. Perry <wmperry@cs.indiana.edu> | ||
| 4 | ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2002, | ||
| 5 | ;; 2007 Free Software Foundation, Inc. | ||
| 6 | |||
| 7 | ;; Author: William M. Perry <wmperry@gnu.org> | ||
| 8 | ;; Dave Love <fx@gnu.org> | ||
| 9 | ;; Keywords: comm, firewalls | ||
| 10 | |||
| 11 | ;; This file is part of GNU Emacs. | ||
| 12 | |||
| 13 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 14 | ;; it under the terms of the GNU General Public License as published by | ||
| 15 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 16 | ;; any later version. | ||
| 17 | |||
| 18 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 21 | ;; GNU General Public License for more details. | ||
| 22 | |||
| 23 | ;; You should have received a copy of the GNU General Public License | ||
| 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 25 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| 26 | ;; Boston, MA 02110-1301, USA. | ||
| 27 | |||
| 28 | ;;; Commentary: | ||
| 29 | |||
| 30 | ;; This is an implementation of the SOCKS v5 protocol as defined in | ||
| 31 | ;; RFC 1928. | ||
| 32 | |||
| 33 | ;; TODO | ||
| 34 | ;; - Finish the redirection rules stuff | ||
| 35 | ;; - Implement composition of servers. Recursively evaluate the | ||
| 36 | ;; redirection rules and do SOCKS-over-HTTP and SOCKS-in-SOCKS | ||
| 37 | |||
| 38 | (eval-when-compile | ||
| 39 | (require 'wid-edit)) | ||
| 40 | (require 'custom) | ||
| 41 | |||
| 42 | (if (not (fboundp 'split-string)) | ||
| 43 | (defun split-string (string &optional pattern) | ||
| 44 | "Return a list of substrings of STRING which are separated by PATTERN. | ||
| 45 | If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"." | ||
| 46 | (or pattern | ||
| 47 | (setq pattern "[ \f\t\n\r\v]+")) | ||
| 48 | (let (parts (start 0)) | ||
| 49 | (while (string-match pattern string start) | ||
| 50 | (setq parts (cons (substring string start (match-beginning 0)) parts) | ||
| 51 | start (match-end 0))) | ||
| 52 | (nreverse (cons (substring string start) parts))))) | ||
| 53 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 54 | ;;; Custom widgets | ||
| 55 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 56 | (define-widget 'dynamic-choice 'menu-choice | ||
| 57 | "A pretty simple dynamic dropdown list" | ||
| 58 | :format "%[%t%]: %v" | ||
| 59 | :tag "Network" | ||
| 60 | :case-fold t | ||
| 61 | :void '(item :format "invalid (%t)\n") | ||
| 62 | :value-create 's5-widget-value-create | ||
| 63 | :value-delete 'widget-children-value-delete | ||
| 64 | :value-get 'widget-choice-value-get | ||
| 65 | :value-inline 'widget-choice-value-inline | ||
| 66 | :mouse-down-action 'widget-choice-mouse-down-action | ||
| 67 | :action 'widget-choice-action | ||
| 68 | :error "Make a choice" | ||
| 69 | :validate 'widget-choice-validate | ||
| 70 | :match 's5-dynamic-choice-match | ||
| 71 | :match-inline 's5-dynamic-choice-match-inline) | ||
| 72 | |||
| 73 | (defun s5-dynamic-choice-match (widget value) | ||
| 74 | (let ((choices (funcall (widget-get widget :choice-function))) | ||
| 75 | current found) | ||
| 76 | (while (and choices (not found)) | ||
| 77 | (setq current (car choices) | ||
| 78 | choices (cdr choices) | ||
| 79 | found (widget-apply current :match value))) | ||
| 80 | found)) | ||
| 81 | |||
| 82 | (defun s5-dynamic-choice-match-inline (widget value) | ||
| 83 | (let ((choices (funcall (widget-get widget :choice-function))) | ||
| 84 | current found) | ||
| 85 | (while (and choices (not found)) | ||
| 86 | (setq current (car choices) | ||
| 87 | choices (cdr choices) | ||
| 88 | found (widget-match-inline current value))) | ||
| 89 | found)) | ||
| 90 | |||
| 91 | (defun s5-widget-value-create (widget) | ||
| 92 | (let ((choices (funcall (widget-get widget :choice-function))) | ||
| 93 | (value (widget-get widget :value))) | ||
| 94 | (if (not value) | ||
| 95 | (widget-put widget :value (widget-value (car choices)))) | ||
| 96 | (widget-put widget :args choices) | ||
| 97 | (widget-choice-value-create widget))) | ||
| 98 | |||
| 99 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 100 | ;;; Customization support | ||
| 101 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 102 | (defgroup socks nil | ||
| 103 | "SOCKS Support" | ||
| 104 | :prefix "socks-" | ||
| 105 | :group 'processes) | ||
| 106 | |||
| 107 | '(defcustom socks-server-aliases nil | ||
| 108 | "A list of server aliases for use in access control and filtering rules." | ||
| 109 | :group 'socks | ||
| 110 | :type '(repeat (list :format "%v" | ||
| 111 | :value ("" "" 1080 5) | ||
| 112 | (string :tag "Alias") | ||
| 113 | (string :tag "Hostname/IP Address") | ||
| 114 | (integer :tag "Port #") | ||
| 115 | (choice :tag "SOCKS Version" | ||
| 116 | (integer :tag "SOCKS v4" :value 4) | ||
| 117 | (integer :tag "SOCKS v5" :value 5))))) | ||
| 118 | |||
| 119 | '(defcustom socks-network-aliases | ||
| 120 | '(("Anywhere" (netmask "0.0.0.0" "0.0.0.0"))) | ||
| 121 | "A list of network aliases for use in subsequent rules." | ||
| 122 | :group 'socks | ||
| 123 | :type '(repeat (list :format "%v" | ||
| 124 | :value (netmask "" "255.255.255.0") | ||
| 125 | (string :tag "Alias") | ||
| 126 | (radio-button-choice | ||
| 127 | :format "%v" | ||
| 128 | (list :tag "IP address range" | ||
| 129 | (const :format "" :value range) | ||
| 130 | (string :tag "From") | ||
| 131 | (string :tag "To")) | ||
| 132 | (list :tag "IP address/netmask" | ||
| 133 | (const :format "" :value netmask) | ||
| 134 | (string :tag "IP Address") | ||
| 135 | (string :tag "Netmask")) | ||
| 136 | (list :tag "Domain Name" | ||
| 137 | (const :format "" :value domain) | ||
| 138 | (string :tag "Domain name")) | ||
| 139 | (list :tag "Unique hostname/IP address" | ||
| 140 | (const :format "" :value exact) | ||
| 141 | (string :tag "Hostname/IP Address")))))) | ||
| 142 | |||
| 143 | '(defun s5-servers-filter () | ||
| 144 | (if socks-server-aliases | ||
| 145 | (mapcar (lambda (x) (list 'const :tag (car x) :value (car x))) s5-server-aliases) | ||
| 146 | '((const :tag "No aliases defined" :value nil)))) | ||
| 147 | |||
| 148 | '(defun s5-network-aliases-filter () | ||
| 149 | (mapcar (lambda (x) (list 'const :tag (car x) :value (car x))) | ||
| 150 | socks-network-aliases)) | ||
| 151 | |||
| 152 | '(defcustom socks-redirection-rules | ||
| 153 | nil | ||
| 154 | "A list of redirection rules." | ||
| 155 | :group 'socks | ||
| 156 | :type '(repeat (list :format "%v" | ||
| 157 | :value ("Anywhere" nil) | ||
| 158 | (dynamic-choice :choice-function s5-network-aliases-filter | ||
| 159 | :tag "Destination network") | ||
| 160 | (radio-button-choice | ||
| 161 | :tag "Connection type" | ||
| 162 | (const :tag "Direct connection" :value nil) | ||
| 163 | (dynamic-choice :format "%t: %[%v%]" | ||
| 164 | :choice-function s5-servers-filter | ||
| 165 | :tag "Proxy chain via"))))) | ||
| 166 | |||
| 167 | (defcustom socks-server | ||
| 168 | (list "Default server" "socks" 1080 5) | ||
| 169 | "" | ||
| 170 | :group 'socks | ||
| 171 | :type '(list | ||
| 172 | (string :format "" :value "Default server") | ||
| 173 | (string :tag "Server") | ||
| 174 | (integer :tag "Port") | ||
| 175 | (radio-button-choice :tag "SOCKS Version" | ||
| 176 | :format "%t: %v" | ||
| 177 | (const :tag "SOCKS v4 " :format "%t" :value 4) | ||
| 178 | (const :tag "SOCKS v5" :format "%t" :value 5)))) | ||
| 179 | |||
| 180 | |||
| 181 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 182 | ;;; Get down to the nitty gritty | ||
| 183 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 184 | (defconst socks-version 5) | ||
| 185 | (defvar socks-debug nil) | ||
| 186 | |||
| 187 | ;; Common socks v5 commands | ||
| 188 | (defconst socks-connect-command 1) | ||
| 189 | (defconst socks-bind-command 2) | ||
| 190 | (defconst socks-udp-associate-command 3) | ||
| 191 | |||
| 192 | ;; Miscellaneous other socks constants | ||
| 193 | (defconst socks-authentication-null 0) | ||
| 194 | (defconst socks-authentication-failure 255) | ||
| 195 | |||
| 196 | ;; Response codes | ||
| 197 | (defconst socks-response-success 0) | ||
| 198 | (defconst socks-response-general-failure 1) | ||
| 199 | (defconst socks-response-access-denied 2) | ||
| 200 | (defconst socks-response-network-unreachable 3) | ||
| 201 | (defconst socks-response-host-unreachable 4) | ||
| 202 | (defconst socks-response-connection-refused 5) | ||
| 203 | (defconst socks-response-ttl-expired 6) | ||
| 204 | (defconst socks-response-cmd-not-supported 7) | ||
| 205 | (defconst socks-response-address-not-supported 8) | ||
| 206 | |||
| 207 | (defvar socks-errors | ||
| 208 | '("Succeeded" | ||
| 209 | "General SOCKS server failure" | ||
| 210 | "Connection not allowed by ruleset" | ||
| 211 | "Network unreachable" | ||
| 212 | "Host unreachable" | ||
| 213 | "Connection refused" | ||
| 214 | "Time-to-live expired" | ||
| 215 | "Command not supported" | ||
| 216 | "Address type not supported")) | ||
| 217 | |||
| 218 | ;; The socks v5 address types | ||
| 219 | (defconst socks-address-type-v4 1) | ||
| 220 | (defconst socks-address-type-name 3) | ||
| 221 | (defconst socks-address-type-v6 4) | ||
| 222 | |||
| 223 | ;; Base variables | ||
| 224 | (defvar socks-timeout 5) | ||
| 225 | (defvar socks-connections (make-hash-table :size 13)) | ||
| 226 | |||
| 227 | ;; Miscellaneous stuff for authentication | ||
| 228 | (defvar socks-authentication-methods nil) | ||
| 229 | (defvar socks-username (user-login-name)) | ||
| 230 | (defvar socks-password nil) | ||
| 231 | |||
| 232 | (defun socks-register-authentication-method (id desc callback) | ||
| 233 | (let ((old (assq id socks-authentication-methods))) | ||
| 234 | (if old | ||
| 235 | (setcdr old (cons desc callback)) | ||
| 236 | (setq socks-authentication-methods | ||
| 237 | (cons (cons id (cons desc callback)) | ||
| 238 | socks-authentication-methods))))) | ||
| 239 | |||
| 240 | (defun socks-unregister-authentication-method (id) | ||
| 241 | (let ((old (assq id socks-authentication-methods))) | ||
| 242 | (if old | ||
| 243 | (setq socks-authentication-methods | ||
| 244 | (delq old socks-authentication-methods))))) | ||
| 245 | |||
| 246 | (socks-register-authentication-method 0 "No authentication" 'identity) | ||
| 247 | |||
| 248 | (defun socks-build-auth-list () | ||
| 249 | (let ((num 0) | ||
| 250 | (retval "")) | ||
| 251 | (mapcar | ||
| 252 | (function | ||
| 253 | (lambda (x) | ||
| 254 | (if (fboundp (cdr (cdr x))) | ||
| 255 | (setq retval (format "%s%c" retval (car x)) | ||
| 256 | num (1+ num))))) | ||
| 257 | (reverse socks-authentication-methods)) | ||
| 258 | (format "%c%s" num retval))) | ||
| 259 | |||
| 260 | (defconst socks-state-waiting-for-auth 0) | ||
| 261 | (defconst socks-state-submethod-negotiation 1) | ||
| 262 | (defconst socks-state-authenticated 2) | ||
| 263 | (defconst socks-state-waiting 3) | ||
| 264 | (defconst socks-state-connected 4) | ||
| 265 | |||
| 266 | (defmacro socks-wait-for-state-change (proc htable cur-state) | ||
| 267 | (` | ||
| 268 | (while (and (= (gethash 'state (, htable)) (, cur-state)) | ||
| 269 | (memq (process-status (, proc)) '(run open))) | ||
| 270 | (accept-process-output (, proc) socks-timeout)))) | ||
| 271 | |||
| 272 | (defun socks-filter (proc string) | ||
| 273 | (let ((info (gethash proc socks-connections)) | ||
| 274 | state version desired-len) | ||
| 275 | (or info (error "socks-filter called on non-SOCKS connection %S" proc)) | ||
| 276 | (setq state (gethash 'state info)) | ||
| 277 | (cond | ||
| 278 | ((= state socks-state-waiting-for-auth) | ||
| 279 | (puthash 'scratch (concat string (gethash 'scratch info)) info) | ||
| 280 | (setq string (gethash 'scratch info)) | ||
| 281 | (if (< (length string) 2) | ||
| 282 | nil ; We need to spin some more | ||
| 283 | (puthash 'authtype (aref string 1) info) | ||
| 284 | (puthash 'scratch (substring string 2 nil) info) | ||
| 285 | (puthash 'state socks-state-submethod-negotiation info))) | ||
| 286 | ((= state socks-state-submethod-negotiation) | ||
| 287 | ) | ||
| 288 | ((= state socks-state-authenticated) | ||
| 289 | ) | ||
| 290 | ((= state socks-state-waiting) | ||
| 291 | (puthash 'scratch (concat string (gethash 'scratch info)) info) | ||
| 292 | (setq string (gethash 'scratch info)) | ||
| 293 | (setq version (gethash 'server-protocol info)) | ||
| 294 | (cond | ||
| 295 | ((equal version 'http) | ||
| 296 | (if (not (string-match "\r\n\r\n" string)) | ||
| 297 | nil ; Need to spin some more | ||
| 298 | (puthash 'state socks-state-connected info) | ||
| 299 | (puthash 'reply 0 info) | ||
| 300 | (puthash 'response string info))) | ||
| 301 | ((equal version 4) | ||
| 302 | (if (< (length string) 2) | ||
| 303 | nil ; Can't know how much to read yet | ||
| 304 | (setq desired-len | ||
| 305 | (+ 4 ; address length | ||
| 306 | 2 ; port | ||
| 307 | 2 ; initial data | ||
| 308 | )) | ||
| 309 | (if (< (length string) desired-len) | ||
| 310 | nil ; need to spin some more | ||
| 311 | (let ((response (aref string 1))) | ||
| 312 | (if (= response 90) | ||
| 313 | (setq response 0)) | ||
| 314 | (puthash 'state socks-state-connected info) | ||
| 315 | (puthash 'reply response info) | ||
| 316 | (puthash 'response string info))))) | ||
| 317 | ((equal version 5) | ||
| 318 | (if (< (length string) 4) | ||
| 319 | nil | ||
| 320 | (setq desired-len | ||
| 321 | (+ 6 ; Standard socks header | ||
| 322 | (cond | ||
| 323 | ((= (aref string 3) socks-address-type-v4) 4) | ||
| 324 | ((= (aref string 3) socks-address-type-v6) 16) | ||
| 325 | ((= (aref string 3) socks-address-type-name) | ||
| 326 | (if (< (length string) 5) | ||
| 327 | 255 | ||
| 328 | (+ 1 (aref string 4))))))) | ||
| 329 | (if (< (length string) desired-len) | ||
| 330 | nil ; Need to spin some more | ||
| 331 | (puthash 'state socks-state-connected info) | ||
| 332 | (puthash 'reply (aref string 1) info) | ||
| 333 | (puthash 'response string info)))))) | ||
| 334 | ((= state socks-state-connected) | ||
| 335 | ) | ||
| 336 | ) | ||
| 337 | ) | ||
| 338 | ) | ||
| 339 | |||
| 340 | (defun socks-open-connection (server-info) | ||
| 341 | (interactive) | ||
| 342 | (save-excursion | ||
| 343 | (let ((proc (socks-original-open-network-stream "socks" | ||
| 344 | nil | ||
| 345 | (nth 1 server-info) | ||
| 346 | (nth 2 server-info))) | ||
| 347 | (info (make-hash-table :size 13)) | ||
| 348 | (authtype nil) | ||
| 349 | version) | ||
| 350 | |||
| 351 | ;; Initialize process and info about the process | ||
| 352 | (set-process-filter proc 'socks-filter) | ||
| 353 | (set-process-query-on-exit-flag proc nil) | ||
| 354 | (puthash proc info socks-connections) | ||
| 355 | (puthash 'state socks-state-waiting-for-auth info) | ||
| 356 | (puthash 'authtype socks-authentication-failure info) | ||
| 357 | (puthash 'server-protocol (nth 3 server-info) info) | ||
| 358 | (puthash 'server-name (nth 1 server-info) info) | ||
| 359 | (setq version (nth 3 server-info)) | ||
| 360 | (cond | ||
| 361 | ((equal version 'http) | ||
| 362 | ;; Don't really have to do any connection setup under http | ||
| 363 | nil) | ||
| 364 | ((equal version 4) | ||
| 365 | ;; Don't really have to do any connection setup under v4 | ||
| 366 | nil) | ||
| 367 | ((equal version 5) | ||
| 368 | ;; Need to handle all the authentication crap under v5 | ||
| 369 | ;; Send what we think we can handle for authentication types | ||
| 370 | (process-send-string proc (format "%c%s" socks-version | ||
| 371 | (socks-build-auth-list))) | ||
| 372 | |||
| 373 | ;; Basically just do a select() until we change states. | ||
| 374 | (socks-wait-for-state-change proc info socks-state-waiting-for-auth) | ||
| 375 | (setq authtype (gethash 'authtype info)) | ||
| 376 | (cond | ||
| 377 | ((= authtype socks-authentication-null) | ||
| 378 | (and socks-debug (message "No authentication necessary"))) | ||
| 379 | ((= authtype socks-authentication-failure) | ||
| 380 | (error "No acceptable authentication methods found.")) | ||
| 381 | (t | ||
| 382 | (let* ((auth-type (gethash 'authtype info)) | ||
| 383 | (auth-handler (assoc auth-type socks-authentication-methods)) | ||
| 384 | (auth-func (and auth-handler (cdr (cdr auth-handler)))) | ||
| 385 | (auth-desc (and auth-handler (car (cdr auth-handler))))) | ||
| 386 | (set-process-filter proc nil) | ||
| 387 | (if (and auth-func (fboundp auth-func) | ||
| 388 | (funcall auth-func proc)) | ||
| 389 | nil ; We succeeded! | ||
| 390 | (delete-process proc) | ||
| 391 | (error "Failed to use auth method: %s (%d)" | ||
| 392 | (or auth-desc "Unknown") auth-type)) | ||
| 393 | ) | ||
| 394 | ) | ||
| 395 | ) | ||
| 396 | (puthash 'state socks-state-authenticated info) | ||
| 397 | (set-process-filter proc 'socks-filter))) | ||
| 398 | proc))) | ||
| 399 | |||
| 400 | (defun socks-send-command (proc command atype address port) | ||
| 401 | (let ((addr (cond | ||
| 402 | ((or (= atype socks-address-type-v4) | ||
| 403 | (= atype socks-address-type-v6)) | ||
| 404 | address) | ||
| 405 | ((= atype socks-address-type-name) | ||
| 406 | (format "%c%s" (length address) address)) | ||
| 407 | (t | ||
| 408 | (error "Unkown address type: %d" atype)))) | ||
| 409 | (info (gethash proc socks-connections)) | ||
| 410 | request version) | ||
| 411 | (or info (error "socks-send-command called on non-SOCKS connection %S" | ||
| 412 | proc)) | ||
| 413 | (puthash 'state socks-state-waiting info) | ||
| 414 | (setq version (gethash 'server-protocol info)) | ||
| 415 | (cond | ||
| 416 | ((equal version 'http) | ||
| 417 | (setq request (format (eval-when-compile | ||
| 418 | (concat | ||
| 419 | "CONNECT %s:%d HTTP/1.0\r\n" | ||
| 420 | "User-Agent: Emacs/SOCKS v1.0\r\n" | ||
| 421 | "\r\n")) | ||
| 422 | (cond | ||
| 423 | ((equal atype socks-address-type-name) address) | ||
| 424 | (t | ||
| 425 | (error "Unsupported address type for HTTP: %d" atype))) | ||
| 426 | port))) | ||
| 427 | ((equal version 4) | ||
| 428 | (setq request (format | ||
| 429 | "%c%c%c%c%s%s%c" | ||
| 430 | version ; version | ||
| 431 | command ; command | ||
| 432 | (lsh port -8) ; port, high byte | ||
| 433 | (- port (lsh (lsh port -8) 8)) ; port, low byte | ||
| 434 | addr ; address | ||
| 435 | (user-full-name) ; username | ||
| 436 | 0 ; terminate username | ||
| 437 | ))) | ||
| 438 | ((equal version 5) | ||
| 439 | (setq request (format | ||
| 440 | "%c%c%c%c%s%c%c" | ||
| 441 | version ; version | ||
| 442 | command ; command | ||
| 443 | 0 ; reserved | ||
| 444 | atype ; address type | ||
| 445 | addr ; address | ||
| 446 | (lsh port -8) ; port, high byte | ||
| 447 | (- port (lsh (lsh port -8) 8)) ; port, low byte | ||
| 448 | ))) | ||
| 449 | (t | ||
| 450 | (error "Unknown protocol version: %d" version))) | ||
| 451 | (process-send-string proc request) | ||
| 452 | (socks-wait-for-state-change proc info socks-state-waiting) | ||
| 453 | (process-status proc) | ||
| 454 | (if (= (or (gethash 'reply info) 1) socks-response-success) | ||
| 455 | nil ; Sweet sweet success! | ||
| 456 | (delete-process proc) | ||
| 457 | (error "SOCKS: %s" (nth (or (gethash 'reply info) 1) socks-errors))) | ||
| 458 | proc)) | ||
| 459 | |||
| 460 | |||
| 461 | ;; Replacement functions for open-network-stream, etc. | ||
| 462 | (defvar socks-noproxy nil | ||
| 463 | "*List of regexps matching hosts that we should not socksify connections to") | ||
| 464 | |||
| 465 | (defun socks-find-route (host service) | ||
| 466 | (let ((route socks-server) | ||
| 467 | (noproxy socks-noproxy)) | ||
| 468 | (while noproxy | ||
| 469 | (if (eq ?! (aref (car noproxy) 0)) | ||
| 470 | (if (string-match (substring (car noproxy) 1) host) | ||
| 471 | (setq noproxy nil)) | ||
| 472 | (if (string-match (car noproxy) host) | ||
| 473 | (setq route nil | ||
| 474 | noproxy nil))) | ||
| 475 | (setq noproxy (cdr noproxy))) | ||
| 476 | route)) | ||
| 477 | |||
| 478 | (defvar socks-override-functions nil | ||
| 479 | "*Whether to overwrite the open-network-stream function with the SOCKSified | ||
| 480 | version.") | ||
| 481 | |||
| 482 | (if (fboundp 'socks-original-open-network-stream) | ||
| 483 | nil ; Do nothing, we've been here already | ||
| 484 | (defalias 'socks-original-open-network-stream | ||
| 485 | (symbol-function 'open-network-stream)) | ||
| 486 | (if socks-override-functions | ||
| 487 | (defalias 'open-network-stream 'socks-open-network-stream))) | ||
| 488 | |||
| 489 | (defvar socks-services-file "/etc/services") | ||
| 490 | (defvar socks-tcp-services (make-hash-table :size 13 :test 'equal)) | ||
| 491 | (defvar socks-udp-services (make-hash-table :size 13 :test 'equal)) | ||
| 492 | |||
| 493 | (defun socks-parse-services () | ||
| 494 | (if (not (and (file-exists-p socks-services-file) | ||
| 495 | (file-readable-p socks-services-file))) | ||
| 496 | (error "Could not find services file: %s" socks-services-file)) | ||
| 497 | (save-excursion | ||
| 498 | (clrhash socks-tcp-services) | ||
| 499 | (clrhash socks-udp-services) | ||
| 500 | (set-buffer (get-buffer-create " *socks-tmp*")) | ||
| 501 | (erase-buffer) | ||
| 502 | (insert-file-contents socks-services-file) | ||
| 503 | ;; Nuke comments | ||
| 504 | (goto-char (point-min)) | ||
| 505 | (while (re-search-forward "#.*" nil t) | ||
| 506 | (replace-match "")) | ||
| 507 | ;; Nuke empty lines | ||
| 508 | (goto-char (point-min)) | ||
| 509 | (while (re-search-forward "^[ \t\n]+" nil t) | ||
| 510 | (replace-match "")) | ||
| 511 | ;; Now find all the lines | ||
| 512 | (goto-char (point-min)) | ||
| 513 | (let (name port type) | ||
| 514 | (while (re-search-forward "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)/\\([a-z]+\\)" | ||
| 515 | nil t) | ||
| 516 | (setq name (downcase (match-string 1)) | ||
| 517 | port (string-to-number (match-string 2)) | ||
| 518 | type (downcase (match-string 3))) | ||
| 519 | (puthash name port (if (equal type "udp") | ||
| 520 | socks-udp-services | ||
| 521 | socks-tcp-services)))))) | ||
| 522 | |||
| 523 | (defun socks-find-services-entry (service &optional udp) | ||
| 524 | "Return the port # associated with SERVICE" | ||
| 525 | (if (= (hash-table-count socks-tcp-services) 0) | ||
| 526 | (socks-parse-services)) | ||
| 527 | (gethash (downcase service) | ||
| 528 | (if udp socks-udp-services socks-tcp-services))) | ||
| 529 | |||
| 530 | (defun socks-open-network-stream (name buffer host service) | ||
| 531 | (let* ((route (socks-find-route host service)) | ||
| 532 | proc info version atype) | ||
| 533 | (if (not route) | ||
| 534 | (socks-original-open-network-stream name buffer host service) | ||
| 535 | (setq proc (socks-open-connection route) | ||
| 536 | info (gethash proc socks-connections) | ||
| 537 | version (gethash 'server-protocol info)) | ||
| 538 | (cond | ||
| 539 | ((equal version 4) | ||
| 540 | (setq host (socks-nslookup-host host)) | ||
| 541 | (if (not (listp host)) | ||
| 542 | (error "Could not get IP address for: %s" host)) | ||
| 543 | (setq host (apply 'format "%c%c%c%c" host)) | ||
| 544 | (setq atype socks-address-type-v4)) | ||
| 545 | (t | ||
| 546 | (setq atype socks-address-type-name))) | ||
| 547 | (socks-send-command proc | ||
| 548 | socks-connect-command | ||
| 549 | atype | ||
| 550 | host | ||
| 551 | (if (stringp service) | ||
| 552 | (socks-find-services-entry service) | ||
| 553 | service)) | ||
| 554 | (puthash 'buffer buffer info) | ||
| 555 | (puthash 'host host info) | ||
| 556 | (puthash 'service host info) | ||
| 557 | (set-process-filter proc nil) | ||
| 558 | (set-process-buffer proc (if buffer (get-buffer-create buffer))) | ||
| 559 | proc))) | ||
| 560 | |||
| 561 | ;; Authentication modules go here | ||
| 562 | |||
| 563 | ;; Basic username/password authentication, ala RFC 1929 | ||
| 564 | (socks-register-authentication-method 2 "Username/Password" | ||
| 565 | 'socks-username/password-auth) | ||
| 566 | |||
| 567 | (defconst socks-username/password-auth-version 1) | ||
| 568 | |||
| 569 | (defun socks-username/password-auth-filter (proc str) | ||
| 570 | (let ((info (gethash proc socks-connections)) | ||
| 571 | state desired-len) | ||
| 572 | (or info (error "socks-filter called on non-SOCKS connection %S" proc)) | ||
| 573 | (setq state (gethash 'state info)) | ||
| 574 | (puthash 'scratch (concat (gethash 'scratch info) str) info) | ||
| 575 | (if (< (length (gethash 'scratch info)) 2) | ||
| 576 | nil | ||
| 577 | (puthash 'password-auth-status (aref (gethash 'scratch info) 1) info) | ||
| 578 | (puthash 'state socks-state-authenticated info)))) | ||
| 579 | |||
| 580 | (defun socks-username/password-auth (proc) | ||
| 581 | (let* ((info (gethash proc socks-connections)) | ||
| 582 | (state (gethash 'state info))) | ||
| 583 | (if (not socks-password) | ||
| 584 | (setq socks-password (read-passwd | ||
| 585 | (format "Password for %s@%s: " | ||
| 586 | socks-username | ||
| 587 | (gethash 'server-name info))))) | ||
| 588 | (puthash 'scratch "" info) | ||
| 589 | (set-process-filter proc 'socks-username/password-auth-filter) | ||
| 590 | (process-send-string proc | ||
| 591 | (format "%c%c%s%c%s" | ||
| 592 | socks-username/password-auth-version | ||
| 593 | (length socks-username) | ||
| 594 | socks-username | ||
| 595 | (length socks-password) | ||
| 596 | socks-password)) | ||
| 597 | (socks-wait-for-state-change proc info state) | ||
| 598 | (= (gethash 'password-auth-status info) 0))) | ||
| 599 | |||
| 600 | |||
| 601 | ;; More advanced GSS/API stuff, not yet implemented - volunteers? | ||
| 602 | ;; (socks-register-authentication-method 1 "GSS/API" 'socks-gssapi-auth) | ||
| 603 | |||
| 604 | (defun socks-gssapi-auth (proc) | ||
| 605 | nil) | ||
| 606 | |||
| 607 | |||
| 608 | ;; CHAP stuff | ||
| 609 | ;; (socks-register-authentication-method 3 "CHAP" 'socks-chap-auth) | ||
| 610 | (defun socks-chap-auth (proc) | ||
| 611 | nil) | ||
| 612 | |||
| 613 | |||
| 614 | ;; CRAM stuff | ||
| 615 | ;; (socks-register-authentication-method 5 "CRAM" 'socks-cram-auth) | ||
| 616 | (defun socks-cram-auth (proc) | ||
| 617 | nil) | ||
| 618 | |||
| 619 | |||
| 620 | (defcustom socks-nslookup-program "nslookup" | ||
| 621 | "*If non-NIL then a string naming the nslookup program." | ||
| 622 | :type '(choice (const :tag "None" :value nil) string) | ||
| 623 | :group 'socks) | ||
| 624 | |||
| 625 | (defun socks-nslookup-host (host) | ||
| 626 | "Attempt to resolve the given HOSTNAME using nslookup if possible." | ||
| 627 | (interactive "sHost: ") | ||
| 628 | (if socks-nslookup-program | ||
| 629 | (let ((proc (start-process " *nslookup*" " *nslookup*" | ||
| 630 | socks-nslookup-program host)) | ||
| 631 | (res host)) | ||
| 632 | (set-process-query-on-exit-flag proc nil) | ||
| 633 | (save-excursion | ||
| 634 | (set-buffer (process-buffer proc)) | ||
| 635 | (while (progn | ||
| 636 | (accept-process-output proc) | ||
| 637 | (memq (process-status proc) '(run open)))) | ||
| 638 | (goto-char (point-min)) | ||
| 639 | (if (re-search-forward "Name:.*\nAddress\\(es\\)?: *\\([0-9.]+\\)$" nil t) | ||
| 640 | (progn | ||
| 641 | (setq res (buffer-substring (match-beginning 2) | ||
| 642 | (match-end 2)) | ||
| 643 | res (mapcar 'string-to-int (split-string res "\\."))))) | ||
| 644 | (kill-buffer (current-buffer))) | ||
| 645 | res) | ||
| 646 | host)) | ||
| 647 | |||
| 648 | (provide 'socks) | ||
| 649 | |||
| 650 | ;;; socks.el ends here | ||