1 ;; jabber-conn.el - Network transport functions -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2005 - Georg Lehner - jorge@magma.com.ni
4 ;; mostly inspired by Gnus.
6 ;; Copyright (C) 2005 - Carl Henrik Lunde - chlunde+jabber+@ping.uio.no
9 ;; This file is a part of jabber.el.
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2 of the License, or
14 ;; (at your option) any later version.
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, write to the Free Software
23 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 ;; A collection of functions, that hide the details of transmitting to
26 ;; and from a Jabber Server. Mostly inspired by Gnus.
28 (eval-when-compile (require 'cl-lib))
30 ;; Emacs 24 can be linked with GnuTLS
31 (ignore-errors (require 'gnutls))
33 ;; Try two different TLS/SSL libraries, but don't fail if none available.
34 (or (ignore-errors (require 'tls))
35 (ignore-errors (require 'ssl)))
37 (ignore-errors (require 'starttls))
41 (defgroup jabber-conn nil "Jabber Connection Settings."
44 (defun jabber-have-starttls ()
45 "Return non-nil if we can use STARTTLS."
46 (or (and (fboundp 'gnutls-available-p)
48 (and (featurep 'starttls)
49 (or (and (bound-and-true-p starttls-gnutls-program)
50 (executable-find starttls-gnutls-program))
51 (and (bound-and-true-p starttls-program)
52 (executable-find starttls-program))))))
54 (defconst jabber-default-connection-type
56 ;; Use STARTTLS if we can...
57 ((jabber-have-starttls)
59 ;; ...else default to unencrypted connection.
62 "Default connection type.
63 See `jabber-connect-methods'.")
65 (defcustom jabber-connection-ssl-program nil
66 "Program used for SSL/TLS connections.
67 nil means prefer gnutls but fall back to openssl.
68 'gnutls' means use gnutls (through `open-tls-stream').
69 'openssl means use openssl (through `open-ssl-stream')."
70 :type '(choice (const :tag "Prefer gnutls, fall back to openssl" nil)
71 (const :tag "Use gnutls" gnutls)
72 (const :tag "Use openssl" openssl))
75 (defcustom jabber-invalid-certificate-servers ()
76 "Jabber servers for which we accept invalid TLS certificates.
77 This is a list of server names, each matching the hostname part
80 This option has effect only when using native GnuTLS in Emacs 24
82 :type '(repeat string)
85 (defvar jabber-connect-methods
86 `((network jabber-network-connect jabber-network-send)
88 ,(if (and (fboundp 'gnutls-available-p)
90 ;; With "native" TLS, we can use a normal connection.
91 'jabber-network-connect
92 'jabber-starttls-connect)
94 (ssl jabber-ssl-connect jabber-ssl-send)
95 (virtual jabber-virtual-connect jabber-virtual-send))
96 "Alist of connection methods and functions.
97 First item is the symbol naming the method.
98 Second item is the connect function.
99 Third item is the send function.")
101 (defun jabber-get-connect-function (type)
102 "Get the connect function associated with TYPE.
103 TYPE is a symbol; see `jabber-connection-type'."
104 (let ((entry (assq type jabber-connect-methods)))
107 (defun jabber-get-send-function (type)
108 "Get the send function associated with TYPE.
109 TYPE is a symbol; see `jabber-connection-type'."
110 (let ((entry (assq type jabber-connect-methods)))
113 (defun jabber-srv-targets (server network-server port)
114 "Find host and port to connect to.
115 If NETWORK-SERVER and/or PORT are specified, use them.
116 If we can't find SRV records, use standard defaults."
117 ;; If the user has specified a host or a port, obey that.
118 (if (or network-server port)
119 (list (cons (or network-server server)
121 (or (condition-case nil
122 (srv-lookup (concat "_xmpp-client._tcp." server))
124 (list (cons server 5222)))))
126 ;; Plain TCP/IP connection
127 (defun jabber-network-connect (fsm server network-server port)
128 "Connect to a Jabber server with a plain network connection.
129 Send a message of the form (:connected CONNECTION) to FSM if
130 connection succeeds. Send a message (:connection-failed ERRORS) if
133 ((featurep 'make-network-process '(:nowait t))
134 ;; We can connect asynchronously!
135 (jabber-network-connect-async fsm server network-server port))
137 ;; Connecting to the server will block Emacs.
138 (jabber-network-connect-sync fsm server network-server port))))
140 (defun jabber-network-connect-async (fsm server network-server port)
141 ;; Get all potential targets...
142 (let ((targets (jabber-srv-targets server network-server port))
145 ;; ...and connect to them one after another, asynchronously, until
146 ;; connection succeeds.
149 (target remaining-targets)
150 (let ((target target) (remaining-targets remaining-targets))
151 (cl-labels ((connection-successful
153 ;; This mustn't be `fsm-send-sync', because the FSM
154 ;; needs to change the sentinel, which cannot be done
155 ;; from inside the sentinel.
156 (fsm-send fsm (list :connected c)))
159 (when (and (> (length status) 0)
160 (eq (aref status (1- (length status))) ?\n))
161 (setq status (substring status 0 -1)))
163 (format "Couldn't connect to %s:%s: %s"
164 (car target) (cdr target) status)))
167 (when c (delete-process c))
168 (if remaining-targets
171 "Connecting to %s:%s..."
172 (caar remaining-targets) (cdar remaining-targets))
173 (connect (car remaining-targets) (cdr remaining-targets)))
174 (fsm-send fsm (list :connection-failed (nreverse errors))))))
176 (make-network-process
178 :buffer (generate-new-buffer jabber-process-buffer)
179 :host (car target) :service (cdr target)
183 (let ((_target target) (_remaining-targets remaining-targets))
184 (lambda (connection status)
186 ((string-match "^open" status)
187 (connection-successful connection))
188 ((string-match "^failed" status)
189 (connection-failed connection status))
190 ((string-match "^deleted" status)
191 ;; This happens when we delete a process in the
192 ;; "failed" case above.
195 (message "Unknown sentinel status `%s'" status))))))
197 ;; A file-error has the error message in the third list
199 (connection-failed nil (car (cddr e))))
201 ;; Not sure if we ever get anything but file-errors,
202 ;; but let's make sure we report them:
203 (connection-failed nil (error-message-string e))))))))
204 (message "Connecting to %s:%s..." (caar targets) (cdar targets))
205 (connect (car targets) (cdr targets)))))
207 (defun jabber-network-connect-sync (fsm server network-server port)
208 ;; This code will AFAIK only be used on Windows. Apologies in
209 ;; advance for any bit rot...
210 (let ((coding-system-for-read 'utf-8)
211 (coding-system-for-write 'utf-8)
212 (targets (jabber-srv-targets server network-server port))
215 (dolist (target targets)
217 (let ((process-buffer (generate-new-buffer jabber-process-buffer))
220 (setq connection (open-network-stream
226 (unless (or connection jabber-debug-keep-process-buffers)
227 (kill-buffer process-buffer)))
230 (fsm-send fsm (list :connected connection))
231 (throw 'connected connection)))
233 ;; A file-error has the error message in the third list
235 (let ((err (format "Couldn't connect to %s:%s: %s"
236 (car target) (cdr target)
241 ;; Not sure if we ever get anything but file-errors,
242 ;; but let's make sure we report them:
243 (let ((err (format "Couldn't connect to %s:%s: %s"
244 (car target) (cdr target)
245 (error-message-string e))))
247 (push err errors)))))
248 (fsm-send fsm (list :connection-failed (nreverse errors))))))
250 (defun jabber-network-send (connection string)
251 "Send a string via a plain TCP/IP connection to the Jabber Server."
252 (process-send-string connection string))
254 ;; SSL connection, we use openssl's s_client function for encryption
256 ;; TODO: make this configurable
257 (defun jabber-ssl-connect (fsm server network-server port)
258 "Connect via OpenSSL or GnuTLS to a Jabber Server.
259 Send a message of the form (:connected CONNECTION) to FSM if
260 connection succeeds. Send a message (:connection-failed ERRORS) if
262 (let ((coding-system-for-read 'utf-8)
263 (coding-system-for-write 'utf-8)
266 ((and (memq jabber-connection-ssl-program '(nil gnutls))
267 (fboundp 'open-tls-stream))
269 ((and (memq jabber-connection-ssl-program '(nil openssl))
270 (fboundp 'open-ssl-stream))
273 (error "Neither TLS nor SSL connect functions available"))))
275 (let ((process-buffer (generate-new-buffer jabber-process-buffer))
277 (setq network-server (or network-server server))
278 (setq port (or port 5223))
280 (setq connection (funcall connect-function
287 (format "Couldn't connect to %s:%d: %s" network-server port
288 (error-message-string e)))
289 (message "%s" error-msg)))
290 (unless (or connection jabber-debug-keep-process-buffers)
291 (kill-buffer process-buffer))
293 (fsm-send fsm (list :connected connection))
294 (fsm-send fsm (list :connection-failed
295 (when error-msg (list error-msg))))))))
297 (defun jabber-ssl-send (connection string)
298 "Send a string via an SSL-encrypted connection to the Jabber Server."
299 ;; It seems we need to send a linefeed afterwards.
300 (process-send-string connection string)
301 (process-send-string connection "\n"))
303 (defun jabber-starttls-connect (fsm server network-server port)
304 "Connect via an external GnuTLS process to a Jabber Server.
305 Send a message of the form (:connected CONNECTION) to FSM if
306 connection succeeds. Send a message (:connection-failed ERRORS) if
308 (let ((coding-system-for-read 'utf-8)
309 (coding-system-for-write 'utf-8)
310 (targets (jabber-srv-targets server network-server port))
312 (unless (fboundp 'starttls-open-stream)
313 (error "The starttls.el library is not available"))
315 (dolist (target targets)
317 (let ((process-buffer (generate-new-buffer jabber-process-buffer))
321 (starttls-open-stream
326 (unless (or connection jabber-debug-keep-process-buffers)
327 (kill-buffer process-buffer)))
328 (if (null connection)
329 ;; It seems we don't actually get an error if we
330 ;; can't connect. Let's try to convey some useful
331 ;; information to the user at least.
332 (let ((err (format "Couldn't connect to %s:%s"
333 (car target) (cdr target))))
336 (fsm-send fsm (list :connected connection))
337 (throw 'connected connection)))
339 (let ((err (format "Couldn't connect to %s: %s" target
340 (error-message-string e))))
342 (push err errors)))))
343 (fsm-send fsm (list :connection-failed (nreverse errors))))))
345 (defun jabber-starttls-initiate (fsm)
346 "Initiate a STARTTLS connection."
347 (jabber-send-sexp fsm
348 '(starttls ((xmlns . "urn:ietf:params:xml:ns:xmpp-tls")))))
350 (defun jabber-starttls-process-input (fsm xml-data)
351 "Process result of starttls request.
352 On failure, signal error.
354 XML-DATA is the parsed tree data from the stream (stanzas)
355 obtained from `xml-parse-region'."
357 ((eq (car xml-data) 'proceed)
358 (let* ((state-data (fsm-get-state-data fsm))
359 (connection (plist-get state-data :connection)))
360 ;; Did we use open-network-stream or starttls-open-stream? We
361 ;; can tell by process-type.
362 (cl-case (process-type connection)
364 (let* ((hostname (plist-get state-data :server))
365 (verifyp (not (member hostname jabber-invalid-certificate-servers))))
366 ;; gnutls-negotiate might signal an error, which is caught
370 ;; This is the hostname that the certificate should be valid for:
372 :verify-hostname-error verifyp
373 :verify-error verifyp)))
376 (starttls-negotiate connection)
377 (error "Negotiation failure"))))))
378 ((eq (car xml-data) 'failure)
379 (error "Command rejected by server"))))
381 (defvar *jabber-virtual-server-function* nil
382 "Function to use for sending stanzas on a virtual connection.
383 The function should accept two arguments, the connection object
384 and a string that the connection wants to send.")
386 (defun jabber-virtual-connect (fsm _server _network-server _port)
387 "Connect to a virtual \"server\".
388 Use `*jabber-virtual-server-function*' as send function.
389 FSM is the finite state machine created in jabber.el library."
390 (unless (functionp *jabber-virtual-server-function*)
391 (error "No virtual server function specified"))
392 ;; We pass the fsm itself as "connection object", as that is what a
393 ;; virtual server needs to send stanzas.
394 (fsm-send fsm (list :connected fsm)))
396 (defun jabber-virtual-send (connection string)
397 (funcall *jabber-virtual-server-function* connection string))
399 (provide 'jabber-conn)
400 ;; arch-tag: f95ec240-8cd3-11d9-9dbf-000a95c2fcd0