From c991ea14162921e3459fe6859234b5c1c1782a68 Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 16 Sep 2024 18:27:58 -0700 Subject: [PATCH 0/4] *** NOT A PATCH *** *** BLURB HERE *** F. Jason Park (4): Only conditionally resolve hosts in nsm-should-check [POC] Support SOCKS resolve extension [POC] Simplify network-stream openers in socks.el [POC] Integrate the socks and url libraries lisp/net/nsm.el | 33 +++--- lisp/net/socks.el | 200 +++++++++++++++++++++++++++++++---- lisp/url/url-gw.el | 8 +- lisp/url/url-http.el | 19 ++-- lisp/url/url-methods.el | 8 +- lisp/url/url-proxy.el | 22 ++-- lisp/url/url-vars.el | 20 +++- test/lisp/net/socks-tests.el | 110 +++++++++++++++++++ 8 files changed, 356 insertions(+), 64 deletions(-) Interdiff: diff --git a/lisp/net/nsm.el b/lisp/net/nsm.el index a8a3abb6a2d..1ce2ff33ae6 100644 --- a/lisp/net/nsm.el +++ b/lisp/net/nsm.el @@ -231,13 +231,13 @@ nsm-should-check nsm-trust-local-network)) (addresses (network-lookup-address-info host)) (network-interface-list (network-interface-list t))) - (catch 'off-net + (catch 'nsm-should-check (dolist (ip addresses) (dolist (info network-interface-list) (when (nsm-network-same-subnet (substring (nth 1 info) 0 -1) (substring (nth 3 info) 0 -1) (substring ip 0 -1)) - (throw 'off-net t)))))))) + (throw 'nsm-should-check t)))))))) (defun nsm-check-tls-connection (process host port status settings) "Check TLS connection against potential security problems. diff --git a/lisp/net/socks.el b/lisp/net/socks.el index bcec6d98ae2..45685af77ba 100644 --- a/lisp/net/socks.el +++ b/lisp/net/socks.el @@ -562,25 +562,23 @@ socks-server-name-as-tor-service-regexp ;;;###autoload (defun socks-open-network-stream (name buffer host service &rest params) - "Open and return a connection, possibly proxied over SOCKS. + "Open a connection, possibly proxied over SOCKS, and return its process. Expect PARAMS to contain keyword parameters recognized by -`open-network-stream'. Assume HOST and SERVICE refer to the -proxied remote peer rather than the SOCKS server, but assume the -opposite for PARAMS. That is, if PARAMS contains a `:type' of -`tls', treat the underlying connection to the proxy server as -destined for encryption rather than the tunneled connection (even -though `socks-connect-function' has the final say). For TLS with -proxied connections, see the option `socks-proxied-tls-services'. +`open-network-stream'. Assume HOST and SERVICE refer to the proxied +logical peer rather than the local SOCKS server, but assume the opposite +for PARAMS. Signal an error if PARAMS contains a non-nil `:nowait', +which is not supported (although `socks-connect-function' can circumvent +this check). If SERVICE appears in `socks-proxied-tls-services', use +the `gnutls' library to arrange for the proxied connection to be +encrypted, and verify the connection with the `nsm' library. Before connecting, check the HOST against `socks-noproxy'. On -rejection, fall back to a non-SOCKS connection determined by -the variable `socks-connect-function'. - -But, before doing anything, check if `url-using-proxy' is bound -to a `url' struct object, as defined in `url-parse'. If so, -assume it represents the address of the desired SOCKS server -rather than that of the remote peer, and use its fields instead -of `socks-server' for all SOCKS connection details." +rejection, fall back to a non-SOCKS connection determined by the +variable `socks-connect-function'. But before doing anything, check if +`url-using-proxy' is bound to a `url' struct object, as defined in +`url-parse'. If so, assume it represents the address of the desired +SOCKS server rather than that of the remote peer, and use its fields +instead of `socks-server' for all SOCKS connection details." (require 'url-parse) (let* ((url (and (url-p url-using-proxy) (string-prefix-p "socks" (url-type url-using-proxy)) @@ -603,10 +601,12 @@ socks-open-network-stream service (process-contact proc :service))) (certs (plist-get params :client-certificate))) + (when (plist-get :nowait params) + (error "SOCKS: non-nil :nowait parameter not supported")) (socks--initiate-command-connect proc buffer host service) (when (memq port socks-proxied-tls-services) (unless (gnutls-available-p) - (error "GNUTLS required for port %S but missing" port)) + (error "SOCKS: GNUTLS required for port %S but missing" port)) (gnutls-negotiate :process proc :hostname host :keylist (and certs (list certs))) @@ -738,6 +738,8 @@ socks--extract-resolve-response (when-let ((response (process-get proc 'socks-response))) (pcase (process-get proc 'socks-server-protocol) (4 ; https://www.openssh.com/txt/socks4a.protocol + ;; In the latest Tor version (0.4.8.12 in Sept 2024), a query + ;; that normally returns an IPv6 address gives a 91 error. (and-let* (((zerop (process-get proc 'socks-reply))) ((eq (aref response 1) 90)) ; #x5a request granted (a (substring response 4)) ; ignore port for now @@ -753,51 +755,63 @@ socks--extract-resolve-response ;; No reason to support RESOLVE_PTR [F1] extension, right? (3 (let ((len (1- (aref response 4)))) (substring response 5 (+ 5 len)))) - (4 (substring response 4 20))))))) + (4 (socks--convert-address (substring response 4 20)))))))) + +(defun socks--convert-address (input) + "Unpack pairs of bytes into 2-byte words." + ;; This is a poor man's version of: + ;; + ;; (let ((spec (bindat-type (words vec 8 uint 16)))) + ;; (bindat-get-field (bindat-unpack spec input) 'words)) + ;; + (cl-assert (arrayp input)) + (cl-assert (= (length input) 16)) + (let ((result (make-vector 8 0)) + (i -1)) + (while-let (((< (cl-incf i) 8)) + (j (* i 2)) + (hi (aref input j)) + (lo (aref input (1+ j)))) + (aset result i (logior (ash hi 8) lo))) + result)) (declare-function puny-encode-domain "puny" (domain)) -(defun socks--tor-resolve (name &optional _family _flags) - (condition-case err - (if-let ((socks-password (or socks-password "")) - (route (socks-find-route name nil)) - (proc (socks-send-command (socks-open-connection route) - socks-resolve-command - socks-address-type-name - name - 0)) - (ip (prog1 (socks--extract-resolve-response proc) - (delete-process proc)))) - (list (vconcat ip [0])) - (error "Failed to resolve %s" name)) - (error - (unless (member (cadr err) - '("SOCKS: Host unreachable" "SOCKS: Rejected or failed")) - (signal (car err) (cdr err)))))) - +;; This is a hash table containing the most recent lookups. It's +;; cleared every 5 minutes, which is obviously dumb. +;; FIXME use some kind of LRU instead. (defvar socks--tor-resolve-cache nil) (defun socks-tor-resolve (name &optional _family _flags) "Return list with a single IPv4 address for domain NAME. -Return nil on failure. - -See `network-lookup-address-info' for format of return value. As -of 0.4.8.9, TOR's resolution service does not support IPv6. -SOCKS server must support the Tor RESOLVE command. Note that -this function exists for novelty purposes only. Using it in -place of `network-lookup-address-info' or similar may not prevent -DNS leaks." + +See `make-network-process' for the format of the return value. Note +that this function exists for novelty purposes only." (unless (string-match (rx bot (+ ascii) eot) name) (require 'puny) (setq name (puny-encode-domain name))) - ;; FIXME use some kind of LRU here. Currently resets at 5 min. (if socks--tor-resolve-cache (when (time-less-p (car socks--tor-resolve-cache) (current-time)) (clrhash (cdr socks--tor-resolve-cache))) (setq socks--tor-resolve-cache (cons (time-add (* 60 5) (current-time)) (make-hash-table :test #'equal)))) (with-memoization (gethash name (cdr socks--tor-resolve-cache)) - (socks--tor-resolve name))) + (condition-case err + (if-let ((socks-password (or socks-password "")) + (route (socks-find-route name nil)) + (proc (socks-send-command (socks-open-connection route) + socks-resolve-command + socks-address-type-name + name + 0)) + (ip (prog1 (socks--extract-resolve-response proc) + (delete-process proc)))) + (list (vconcat ip [0])) + (error "Failed to resolve %s" name)) + (error + (unless (member (cadr err) '("SOCKS: Host unreachable" + "SOCKS: Rejected or failed")) + (signal (car err) (cdr err))))))) (provide 'socks) diff --git a/test/lisp/net/socks-tests.el b/test/lisp/net/socks-tests.el index c8939d49c7f..7d65188872e 100644 --- a/test/lisp/net/socks-tests.el +++ b/test/lisp/net/socks-tests.el @@ -397,4 +397,44 @@ tor-resolve-5 (kill-buffer (process-buffer server)) (delete-process server))) +;; On the latest Tor (0.4.8.12 as of Sept 2024), a query via tor-resolve +;; that normally returns an IPv6 address instead returns a 91 code. +(ert-deftest tor-resolve-4a-fail/ipv6 () + (let* ((socks-server '("server" "127.0.0.1" t 4a)) + (socks-username "") ; defaults to (user-login-name) + (socks-tests-canned-server-patterns + '(([4 #xf0 0 0 0 0 0 1 0 + ?i ?p ?v ?6 ?. ?g ?o ?o ?g ?l ?e ?. ?c ?o ?m 0] + . [0 91 0 0 0 0 0 0]))) + (inhibit-message noninteractive) + (server (socks-tests-canned-server-create)) + socks--tor-resolve-cache) + (ert-info ("Query TOR RESOLVE service over SOCKS4") + (should-not (socks-tor-resolve "ipv6.google.com"))) + (kill-buffer (process-buffer server)) + (delete-process server))) + +(ert-deftest tor-resolve-5/ipv6 () + "Make request to TOR resolve service over SOCKS5" + (let* ((socks-server '("server" "127.0.0.1" t 5)) + (socks-username "foo") + (socks-authentication-methods + (copy-sequence socks-authentication-methods)) + (inhibit-message noninteractive) + (socks-tests-canned-server-patterns + '(([5 2 0 2] . [5 2]) + ([1 3 ?f ?o ?o 0] . [1 0]) + ([5 #xf0 0 3 15 ?i ?p ?v ?6 ?. ?g ?o ?o ?g ?l ?e ?. ?c ?o ?m 0 0] + . [5 0 0 4 + #x2a #x00 #x14 #x50 #x40 #x0e #x80 #xe0 + #x00 #x00 #x00 #x00 #x00 #x00 #x20 #x0e + 0 0]))) + (server (socks-tests-canned-server-create)) + socks--tor-resolve-cache) + (ert-info ("Query TOR RESOLVE service over SOCKS5") + (should (equal '([#x2a00 #x1450 #x400e #x80e0 0 0 0 #x200e 0]) + (socks-tor-resolve "ipv6.google.com")))) + (kill-buffer (process-buffer server)) + (delete-process server))) + ;;; socks-tests.el ends here -- 2.46.0