From d7cca6703475b8bcdf523407383a813ec3749fad Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 14 Feb 2022 02:36:57 -0800 Subject: [PATCH 1/3] [POC] Support SOCKS resolve extension * lisp/net/socks.el (socks-resolve-command): Add new constant for the RESOLVE command, a nonstandard SOCKS extension from the Tor project. It mirrors CONNECT in most respects but asks the server to RESOLVE a host name and return its IP. For details, see doc/socks/socks-extensions.txt in the source tree for torsocks. This shouldn't be confused with 5h/5-hostname, which is used to by clients like cURL to allow users to bypass attempts to resolve a name locally. (socks--extract-resolve-response, socks-tor-resolve): Add utility functions to query a SOCKS service supporting the RESOLVE extension. * test/lisp/net/socks-tests.el (tor-resolve-4a, tor-resolve-4a-fail, tor-resolve-5-fail, tor-resolve-5): (Bug#53941.) --- lisp/net/socks.el | 69 +++++++++++++++++++++++++++++++++++ test/lisp/net/socks-tests.el | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/lisp/net/socks.el b/lisp/net/socks.el index e572e5c9bdf..4c4eb8cf751 100644 --- a/lisp/net/socks.el +++ b/lisp/net/socks.el @@ -181,6 +181,9 @@ socks-udp-associate-command (defconst socks-authentication-null 0) (defconst socks-authentication-failure 255) +;; Extensions +(defconst socks-resolve-command #xf0) + ;; Response codes (defconst socks-response-success 0) (defconst socks-response-general-failure 1) @@ -655,6 +658,72 @@ socks-nslookup-host res) host)) +(defun socks--extract-resolve-response (proc) + "Parse response for PROC and maybe return destination IP address." + (when-let ((response (process-get proc 'socks-response))) + (pcase (process-get proc 'socks-server-protocol) + (4 ; https://www.openssh.com/txt/socks4a.protocol + (and-let* (((zerop (process-get proc 'socks-reply))) + ((eq (aref response 1) 90)) ; #x5a request granted + (a (substring response 4)) ; ignore port for now + ((not (string-empty-p a))) + ((not (string= a "\0\0\0\0")))) + a)) + (5 ; https://tools.ietf.org/html/rfc1928 + (cl-assert (eq 5 (aref response 0)) t) + (pcase (aref response 3) ; ATYP + (1 (and-let* ((a (substring response 4 8)) + ((not (string= a "\0\0\0\0"))) + a))) + ;; 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))))))) + +(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)))))) + +(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." + (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))) + (provide 'socks) ;;; socks.el ends here diff --git a/test/lisp/net/socks-tests.el b/test/lisp/net/socks-tests.el index 1a4bac37bf9..cc9f5a385d2 100644 --- a/test/lisp/net/socks-tests.el +++ b/test/lisp/net/socks-tests.el @@ -327,4 +327,74 @@ socks-override-functions (should-not (advice-member-p #'socks--open-network-stream 'open-network-stream))) +(ert-deftest tor-resolve-4a () + "Make request to TOR resolve service over SOCKS4a" + (let* ((socks-server '("server" "127.0.0.1" t 4a)) + (socks-username "foo") ; defaults to (user-login-name) + (socks-tests-canned-server-patterns + '(([4 #xf0 0 0 0 0 0 1 ?f ?o ?o 0 ?e ?x ?a ?m ?p ?l ?e ?. ?c ?o ?m 0] + . [0 90 0 0 93 184 216 34]))) + (inhibit-message noninteractive) + (server (socks-tests-canned-server-create)) + socks--tor-resolve-cache) + (ert-info ("Query TOR RESOLVE service over SOCKS4") + (cl-letf (((symbol-function 'user-full-name) + (lambda (&optional _) "foo"))) + (should (equal '([93 184 216 34 0]) + (socks-tor-resolve "example.com"))))) + (kill-buffer (process-buffer server)) + (delete-process server))) + +(ert-deftest tor-resolve-4a-fail () + (let* ((socks-server '("server" "127.0.0.1" t 4a)) + (socks-username "foo") ; defaults to (user-login-name) + (socks-tests-canned-server-patterns + '(([4 #xf0 0 0 0 0 0 1 ?f ?o ?o 0 ?e ?x ?a ?m ?p ?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") + (cl-letf (((symbol-function 'user-full-name) + (lambda (&optional _) "foo"))) + (should-not (socks-tor-resolve "example.com")))) + (kill-buffer (process-buffer server)) + (delete-process server))) + +(ert-deftest tor-resolve-5-fail () + (let* ((socks-server '("server" "127.0.0.1" t 5)) + (socks-username "") + (socks-authentication-methods (copy-sequence + socks-authentication-methods)) + (inhibit-message noninteractive) + (socks-tests-canned-server-patterns + '(([5 2 0 2] . [5 2]) + ([1 0 0] . [1 0]) + ([5 #xf0 0 3 11 ?e ?x ?a ?m ?p ?l ?e ?. ?c ?o ?m 0 0] + . [5 4 0 0 0 0 0 0 0 0]))) + (server (socks-tests-canned-server-create))) + (ert-info ("Query TOR RESOLVE service over SOCKS5") + (should-not (socks-tor-resolve "example.com"))) + (kill-buffer (process-buffer server)) + (delete-process server))) + +(ert-deftest tor-resolve-5 () + "Make request to TOR resolve service over SOCKS5" + (let* ((socks-server '("server" "127.0.0.1" t 5)) + (socks-username "foo") + (socks-authentication-methods (append socks-authentication-methods + nil)) + (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 11 ?e ?x ?a ?m ?p ?l ?e ?. ?c ?o ?m 0 0] + . [5 0 0 1 93 184 216 34 0 0]))) + (server (socks-tests-canned-server-create)) + socks--tor-resolve-cache) + (ert-info ("Query TOR RESOLVE service over SOCKS5") + (should (equal '([93 184 216 34 0]) (socks-tor-resolve "example.com")))) + (kill-buffer (process-buffer server)) + (delete-process server))) + ;;; socks-tests.el ends here -- 2.42.0