Access onion services by a SOCKS proxy (e.g., Tor)
authorSimon Nicolussi <sinic@sinic.name>
Sun, 4 Oct 2020 15:17:53 +0000 (17:17 +0200)
committerSimon Nicolussi <sinic@sinic.name>
Sun, 4 Oct 2020 15:17:53 +0000 (17:17 +0200)
To test this, start the Tor daemon (tor), set the variable socks-server
to the list ("Tor" "localhost" 9050 5), and visit some onion service.

The socks library of Emacs provides a (very limited) alternative to the
regular open-network-stream function. Some of those limitations can be
mitigated by setting the respective options (e.g., the coding system)
later on, instead of when creating the network process object. This is
now done for both indirect and direct connections, for consistency.

TLS is a special case, as the initial connection to the SOCKS server is
typically unencrypted: TLS negotiation only takes place after the proxy
established a connection to the target. The force-ipv4 option is also
a special case, as it is ignored (it obviously cannot work with Tor).

The most serious limitation of the socks library is that opening the
network stream happens synchronously. For this reason, create the timer
before calling socks-open-network-stream. The sentinel function is also
not called automatically in the case of a synchronous open, so call it
explicitly. The appearance of a hanging Emacs is not ideal (even though
users should always be able to abort connection attempts with C-g), but
any improvement probably requires changes in the socks library.

elpher.el

index 4c34f52..2a7648b 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -66,6 +66,7 @@
 (require 'ansi-color)
 (require 'nsm)
 (require 'gnutls)
+(require 'socks)
 
 
 ;;; Global constants
@@ -620,32 +621,18 @@ the host operating system and the local network capabilities."
     (condition-case nil
         (let* ((kill-buffer-query-functions nil)
                (port (elpher-address-port address))
+               (service (if (> port 0) port default-port))
                (host (elpher-address-host address))
+               (socks (string-suffix-p ".onion" host))
                (response-string-parts nil)
                (bytes-received 0)
                (hkbytes-received 0)
-               (proc (make-network-process :name "elpher-process"
-                                           :host host
-                                           :family (and force-ipv4 'ipv4)
-                                           :service (if (> port 0) port default-port)
-                                           :buffer nil
-                                           :coding 'binary
-                                           :noquery t
-                                           :nowait t
-                                           :tls-parameters
-                                           (and use-tls
-                                                (cons 'gnutls-x509pki
-                                                      (gnutls-boot-parameters
-                                                       :type 'gnutls-x509pki
-                                                       :hostname host
-                                                       :keylist
-                                                       (elpher-get-current-keylist address))))))
                (timer (run-at-time elpher-connection-timeout nil
                                    (lambda ()
                                      (elpher-process-cleanup)
                                      (cond
                                         ; Try again with IPv4
-                                      ((not force-ipv4)
+                                      ((not (or force-ipv4 socks))
                                        (message "Connection timed out.  Retrying with IPv4.")
                                        (elpher-get-host-response address default-port
                                                                  query-string
@@ -662,8 +649,24 @@ the host operating system and the local network capabilities."
                                                                  response-processor
                                                                  nil force-ipv4))
                                       (t
-                                       (elpher-network-error address "Connection time-out.")))))))
+                                       (elpher-network-error address "Connection time-out."))))))
+               (gnutls-params (list :type 'gnutls-x509pki :hostname host
+                                    :keylist (elpher-get-current-keylist address)))
+               (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
+                       (make-network-process :name "elpher-process"
+                                             :host host
+                                             :family (and force-ipv4 'ipv4)
+                                             :service service
+                                             :buffer nil
+                                             :nowait t
+                                             :tls-parameters
+                                             (and use-tls
+                                                  (cons 'gnutls-x509pki
+                                                        (apply #'gnutls-boot-parameters
+                                                               gnutls-params)))))))
           (setq elpher-network-timer timer)
+          (set-process-coding-system proc 'binary 'binary)
+          (set-process-query-on-exit-flag proc nil)
           (elpher-buffer-message (concat "Connecting to " host "..."
                                          " (press 'u' to abort)"))
           (set-process-filter proc
@@ -696,7 +699,7 @@ the host operating system and the local network capabilities."
                                           (process-send-string proc query-string)))
                                        ((string-prefix-p "deleted" event)) ; do nothing
                                        ((and (not response-string-parts)
-                                             (not (or elpher-ipv4-always force-ipv4)))
+                                             (not (or elpher-ipv4-always force-ipv4 socks)))
                                         ; Try again with IPv4
                                         (message "Connection failed. Retrying with IPv4.")
                                         (elpher-get-host-response address default-port
@@ -712,7 +715,10 @@ the host operating system and the local network capabilities."
                                        (t
                                         (error "No response from server")))
                                     (error
-                                     (elpher-network-error address the-error))))))
+                                     (elpher-network-error address the-error)))))
+          (when socks
+            (if use-tls (apply #'gnutls-negotiate :process proc gnutls-params))
+            (funcall (process-sentinel proc) proc "open\n")))
       (error
        (error "Error initiating connection to server")))))