>From e0593f5be91e27541a9a13bad9632696fceb9f2a Mon Sep 17 00:00:00 2001 From: "F. Jason Park" Date: Tue, 1 Mar 2022 02:12:02 -0800 Subject: [PATCH 3/4] Simplify network-stream openers in socks.el * lisp/net/socks.el (socks-override-functions): Make variable obsolete and remove uses throughout. (socks-open-network-stream-fallback): Add new custom option indicating whether to fall back on non-SOCKS connections. (socks-open-network-stream-tls-services): Add new custom option for specifying ports for proxied connections that should be encrypted with TLS. (socks-open-network-stream): Recognize additional `url' struct param. Also prefer parsed URL details when present in `url-using-proxy'. (socks--open-network-stream): Reduce role to merely issuing the first command using an existing process. --- lisp/net/socks.el | 109 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/lisp/net/socks.el b/lisp/net/socks.el index b9af2aa06e..ac732b228b 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 @@ -325,15 +325,15 @@ socks-filter (process-put proc 'socks-response string)))))) ((= state socks-state-connected))))) -;; FIXME this is a terrible idea. -;; It is not even compatible with the argument spec of open-network-stream -;; in 24.1. - (defvar socks-override-functions nil "If non-nil, overwrite `open-network-stream' function with SOCKSified version.") -(when socks-override-functions - (advice-add 'open-network-stream :around #'socks--open-network-stream)) +;; Libraries typically offer a "stream opener" option, such as ERC's +;; `erc-server-connect-function'. These provide a level of +;; flexibility tantamount to what this variable formerly offered. +(make-obsolete-variable + 'socks-override-functions + "see `socks-open-network-stream' and `socks-connect-function'." "29.1") (defcustom socks-connect-function 'open-network-stream "Function to open a network connection to a SOCKS provider. @@ -539,22 +539,85 @@ 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)) +(defcustom socks-open-network-stream-fallback nil + "Whether `socks-open-network-stream' should fall back to non-SOCKS." + :version "29.1" + :type 'boolean) + +(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 "29.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)) + +;;;###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', and on +rejection either signal an error or fall back to non-SOCKS, +depending on the value of `socks-open-network-stream-fallback'. +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)) + (socks-server (if url + (list 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--open-network-stream proc buffer host service) + (if (and (memq port socks-proxied-tls-services) + (gnutls-available-p) + (require 'gnutls nil t) + (require 'nsm nil t)) + (progn (gnutls-negotiate :process proc + :hostname host + :keylist (and certs (list certs))) + (nsm-verify-connection proc host port)) + proc)) + ;; Retain legacy behavior and connect anyway without warning + (if socks-open-network-stream-fallback + (with-suppressed-warnings ((obsolete socks-override-functions)) + (let (socks-override-functions) + (apply #'open-network-stream name buffer host service params))) + (error "Connection rejected by `socks-noproxy'"))))) + +(defun socks--open-network-stream (proc buffer host service) + (progn ; preserve indentation level for git blame / code review + (progn ; could rename to something like `socks--initiate-command-connect' + (let* ((version (process-get proc 'socks-server-protocol)) (atype (cond ((equal version 4) -- 2.38.1