From ce5e185fa9179fb88e36da7064b56befc2ee276c Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Mon, 28 Nov 2022 22:31:50 -0800 Subject: [PATCH 3/4] [POC] Simplify network-stream openers in socks.el * lisp/net/socks.el (socks-connect-function): New variable for specifying an `open-network-stream'-like connect function. (socks-open-connection): Accept additional `open-network-stream' params passed on to opener, now `socks-connect-function', in place of `open-network-stream'. (socks-proxied-tls-services): Add new option for specifying ports whose proxied connections should use TLS. (socks--open-network-stream): Rework to serve as thin wrapper for `socks-open-network-stream' that now hinges on rather than ignores the variable `socks-override-functions'. (socks-open-network-stream): Prefer parsed URL details, when present in a non-nil `url-using-proxy', for improved compatibility with the `gw' framework. (socks--initiate-command-connect): New function to house renamed latter half of the original `socks--open-network-stream'. Role now reduced to issuing the first command using an existing process. (Bug#53941) --- lisp/net/socks.el | 117 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 21 deletions(-) diff --git a/lisp/net/socks.el b/lisp/net/socks.el index b93a114a198..bcec6d98ae2 100644 --- a/lisp/net/socks.el +++ b/lisp/net/socks.el @@ -34,7 +34,7 @@ ;;; Code: -(eval-when-compile (require 'cl-lib)) +(eval-when-compile (require 'cl-lib) (require 'url-parse)) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;; Custom widgets @@ -338,14 +338,20 @@ socks-override-functions (when socks-override-functions (advice-add 'open-network-stream :around #'socks--open-network-stream)) -(defun socks-open-connection (server-info) +(defvar socks-connect-function #'open-network-stream + "Function to open a network connection to a SOCKS provider. +Called with arguments suitable for `open-network-stream'.") + +(defun socks-open-connection (server-info &rest stream-params) + "Create and initialize a SOCKS process. +Perform authentication if needed. Expect SERVER-INFO to resemble +`socks-server' and STREAM-PARAMS to be keyword parameters +accepted by `open-network-stream'." (save-excursion (let ((proc (let ((socks-override-functions nil)) - (open-network-stream "socks" - nil - (nth 1 server-info) - (nth 2 server-info)))) + (apply socks-connect-function (nth 0 server-info) nil + (nth 1 server-info) (nth 2 server-info) stream-params))) (authtype nil) version) @@ -531,22 +537,91 @@ socks-find-services-entry (gethash (downcase service) (if udp socks-udp-services socks-tcp-services))) -(defun socks-open-network-stream (name buffer host service) - (let ((socks-override-functions t)) - (socks--open-network-stream - (lambda (&rest args) - (let ((socks-override-functions nil)) - (apply #'open-network-stream args))) - name buffer host service))) - (defun socks--open-network-stream (orig-fun name buffer host service &rest params) - (let ((route (and socks-override-functions - (socks-find-route host service)))) - (if (not route) - (apply orig-fun name buffer host service params) - ;; FIXME: Obey `params'! - (let* ((proc (socks-open-connection route)) - (version (process-get proc 'socks-server-protocol)) + "Call `socks-open-network-stream', falling back to ORIG-FUN. +Expect NAME, BUFFER, HOST, SERVICE, and PARAMS to be compatible +with `open-network-stream'." + (let ((socks-connect-function orig-fun)) + (apply (if socks-override-functions #'socks-open-network-stream orig-fun) + name buffer host service params))) + +(defcustom socks-proxied-tls-services '(443 6697) + "Ports whose connections should use TLS. +Note that the system resolver may be consulted to look up host +names for checking domain validation certs." + :version "30.1" + :type '(repeat number)) + +(declare-function gnutls-negotiate "gnutls" (&rest rest)) +(declare-function nsm-verify-connection "nsm" + (process host port &optional + save-fingerprint warn-unencrypted)) + +(defvar socks-server-name-as-tor-service-regexp (rx bow "tor" eow) + "Regexp to determine if a `socks-server' entry is TOR service.") + +;;;###autoload +(defun socks-open-network-stream (name buffer host service &rest params) + "Open and return a connection, possibly proxied over SOCKS. +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'. + +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." + (require 'url-parse) + (let* ((url (and (url-p url-using-proxy) + (string-prefix-p "socks" (url-type url-using-proxy)) + url-using-proxy)) + (server-name (and url (string= (nth 1 socks-server) (url-host url)) + (= (nth 2 socks-server) (url-port url)) + (car socks-server))) + (socks-server (if url + (list server-name (url-host url) (url-port url) + (pcase (url-type url) + ("socks4" 4) + ("socks4a" '4a) + (_ 5))) + socks-server)) + (socks-username (or (and url (url-user url)) socks-username)) + (socks-password (or (and url (url-password url)) socks-password))) + (if-let ((route (socks-find-route host service)) + (proc (apply #'socks-open-connection route params))) + (let ((port (if (numberp service) + service + (process-contact proc :service))) + (certs (plist-get params :client-certificate))) + (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)) + (gnutls-negotiate :process proc + :hostname host + :keylist (and certs (list certs))) + (unless (string-suffix-p ".onion" host) + (require 'nsm) + (defvar nsm-trust-local-network) + (let (nsm-trust-local-network) + (nsm-verify-connection proc host port)))) + proc) + (apply socks-connect-function name buffer host service params)))) + +(defun socks--initiate-command-connect (proc buffer host service) + (progn ; preserve indentation level for git blame / code review + (progn + (let* ((version (process-get proc 'socks-server-protocol)) (atype (cond ((equal version 4) -- 2.46.0