NeoMutt  2023-03-22
Teaching an old dog new tricks
DOXYGEN
auth_sasl.c File Reference

IMAP SASL authentication method. More...

#include "config.h"
#include <stddef.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "private.h"
#include "mutt/lib.h"
#include "conn/lib.h"
#include "adata.h"
#include "auth.h"
+ Include dependency graph for auth_sasl.c:

Go to the source code of this file.

Functions

enum ImapAuthRes imap_auth_sasl (struct ImapAccountData *adata, const char *method)
 SASL authenticator - Implements ImapAuth::authenticate() More...
 

Detailed Description

IMAP SASL authentication method.

Authors
  • Brendan Cully

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file auth_sasl.c.

Function Documentation

◆ imap_auth_sasl()

enum ImapAuthRes imap_auth_sasl ( struct ImapAccountData adata,
const char *  method 
)

SASL authenticator - Implements ImapAuth::authenticate()

Definition at line 45 of file auth_sasl.c.

46{
47 sasl_conn_t *saslconn = NULL;
48 sasl_interact_t *interaction = NULL;
49 int rc, irc;
50 char *buf = NULL;
51 size_t bufsize = 0;
52 const char *mech = NULL;
53 const char *pc = NULL;
54 unsigned int len = 0, olen = 0;
55 bool client_start;
56
57 if (mutt_sasl_client_new(adata->conn, &saslconn) < 0)
58 {
59 mutt_debug(LL_DEBUG1, "Error allocating SASL connection\n");
60 return IMAP_AUTH_FAILURE;
61 }
62
63 rc = SASL_FAIL;
64
65 /* If the user hasn't specified a method, use any available */
66 if (!method)
67 {
68 method = adata->capstr;
69
70 /* hack for SASL ANONYMOUS support:
71 * 1. Fetch username. If it's "" or "anonymous" then
72 * 2. attempt sasl_client_start with only "AUTH=ANONYMOUS" capability
73 * 3. if sasl_client_start fails, fall through... */
74
75 if (mutt_account_getuser(&adata->conn->account) < 0)
76 {
77 sasl_dispose(&saslconn);
78 return IMAP_AUTH_FAILURE;
79 }
80
82 (!adata->conn->account.user[0] ||
83 mutt_str_startswith(adata->conn->account.user, "anonymous")))
84 {
85 rc = sasl_client_start(saslconn, "AUTH=ANONYMOUS", NULL, &pc, &olen, &mech);
86 }
87 }
88 else if (mutt_istr_equal("login", method) && !strstr(NONULL(adata->capstr), "AUTH=LOGIN"))
89 {
90 /* do not use SASL login for regular IMAP login */
91 sasl_dispose(&saslconn);
92 return IMAP_AUTH_UNAVAIL;
93 }
94
95 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
96 {
97 do
98 {
99 rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
100 if (rc == SASL_INTERACT)
101 mutt_sasl_interact(interaction);
102 } while (rc == SASL_INTERACT);
103 }
104
105 client_start = (olen > 0);
106
107 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
108 {
109 if (method)
110 mutt_debug(LL_DEBUG2, "%s unavailable\n", method);
111 else
112 {
113 mutt_debug(LL_DEBUG1, "Failure starting authentication exchange. No shared mechanisms?\n");
114 }
115 /* SASL doesn't support LOGIN, so fall back */
116
117 sasl_dispose(&saslconn);
118 return IMAP_AUTH_UNAVAIL;
119 }
120
121 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
122 mutt_message(_("Authenticating (%s)..."), mech);
123
124 bufsize = MAX((olen * 2), 1024);
125 buf = mutt_mem_malloc(bufsize);
126
127 snprintf(buf, bufsize, "AUTHENTICATE %s", mech);
128 if ((adata->capabilities & IMAP_CAP_SASL_IR) && client_start)
129 {
130 len = mutt_str_len(buf);
131 buf[len++] = ' ';
132 if (sasl_encode64(pc, olen, buf + len, bufsize - len, &olen) != SASL_OK)
133 {
134 mutt_debug(LL_DEBUG1, "#1 error base64-encoding client response\n");
135 goto bail;
136 }
137 client_start = false;
138 olen = 0;
139 }
140 imap_cmd_start(adata, buf);
141 irc = IMAP_RES_CONTINUE;
142
143 /* looping protocol */
144 while ((rc == SASL_CONTINUE) || (olen > 0))
145 {
146 do
147 {
148 irc = imap_cmd_step(adata);
149 } while (irc == IMAP_RES_CONTINUE);
150
151 if ((irc == IMAP_RES_BAD) || (irc == IMAP_RES_NO))
152 goto bail;
153
154 if (irc == IMAP_RES_RESPOND)
155 {
156 /* Exchange incorrectly returns +\r\n instead of + \r\n */
157 if (adata->buf[1] == '\0')
158 {
159 buf[0] = '\0';
160 len = 0;
161 }
162 else
163 {
164 len = strlen(adata->buf + 2);
165 if (len > bufsize)
166 {
167 bufsize = len;
168 mutt_mem_realloc(&buf, bufsize);
169 }
170 /* For sasl_decode64, the fourth parameter, outmax, doesn't
171 * include space for the trailing null */
172 if (sasl_decode64(adata->buf + 2, len, buf, bufsize - 1, &len) != SASL_OK)
173 {
174 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
175 goto bail;
176 }
177 }
178 }
179
180 /* client-start is only available with the SASL-IR extension, but
181 * SASL 2.1 seems to want to use it regardless, at least for DIGEST
182 * fast reauth. Override if the server sent an initial continuation */
183 if (!client_start || buf[0])
184 {
185 do
186 {
187 rc = sasl_client_step(saslconn, buf, len, &interaction, &pc, &olen);
188 if (rc == SASL_INTERACT)
189 mutt_sasl_interact(interaction);
190 } while (rc == SASL_INTERACT);
191 }
192 else
193 client_start = false;
194
195 /* send out response, or line break if none needed */
196 if (olen)
197 {
198 if ((olen * 2) > bufsize)
199 {
200 bufsize = olen * 2;
201 mutt_mem_realloc(&buf, bufsize);
202 }
203 if (sasl_encode64(pc, olen, buf, bufsize, &olen) != SASL_OK)
204 {
205 mutt_debug(LL_DEBUG1, "#2 error base64-encoding client response\n");
206 goto bail;
207 }
208 }
209
210 if (irc == IMAP_RES_RESPOND)
211 {
212 mutt_str_copy(buf + olen, "\r\n", bufsize - olen);
213 mutt_socket_send(adata->conn, buf);
214 }
215
216 /* If SASL has errored out, send an abort string to the server */
217 if (rc < 0)
218 {
219 mutt_socket_send(adata->conn, "*\r\n");
220 mutt_debug(LL_DEBUG1, "sasl_client_step error %d\n", rc);
221 }
222
223 olen = 0;
224 }
225
226 while (irc != IMAP_RES_OK)
227 {
228 irc = imap_cmd_step(adata);
229 if (irc != IMAP_RES_CONTINUE)
230 break;
231 }
232
233 if (rc != SASL_OK)
234 goto bail;
235
236 if (imap_code(adata->buf))
237 {
238 mutt_sasl_setup_conn(adata->conn, saslconn);
239 FREE(&buf);
240 return IMAP_AUTH_SUCCESS;
241 }
242
243bail:
244 sasl_dispose(&saslconn);
245 FREE(&buf);
246
247 if (method)
248 {
249 mutt_debug(LL_DEBUG2, "%s failed\n", method);
250 return IMAP_AUTH_UNAVAIL;
251 }
252
253 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
254 mutt_error(_("%s authentication failed"), "SASL ");
255
256 return IMAP_AUTH_FAILURE;
257}
@ IMAP_AUTH_FAILURE
Authentication failed.
Definition: auth.h:40
@ IMAP_AUTH_SUCCESS
Authentication successful.
Definition: auth.h:39
@ IMAP_AUTH_UNAVAIL
Authentication method not permitted.
Definition: auth.h:41
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:49
#define mutt_error(...)
Definition: logging.h:87
#define mutt_message(...)
Definition: logging.h:86
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition: command.c:1062
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1076
bool imap_code(const char *s)
Was the command successful.
Definition: command.c:1199
#define IMAP_RES_RESPOND
+
Definition: private.h:58
#define IMAP_RES_OK
<tag> OK ...
Definition: private.h:56
#define IMAP_CAP_AUTH_ANONYMOUS
AUTH=ANONYMOUS.
Definition: private.h:130
#define IMAP_RES_NO
<tag> NO ...
Definition: private.h:54
#define IMAP_CAP_SASL_IR
SASL initial response draft.
Definition: private.h:136
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:57
#define IMAP_RES_BAD
<tag> BAD ...
Definition: private.h:55
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
#define FREE(x)
Definition: memory.h:43
#define MAX(a, b)
Definition: memory.h:30
#define _(a)
Definition: message.h:28
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:819
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:652
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:694
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:599
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:731
#define mutt_socket_send(conn, buf)
Definition: socket.h:59
#define NONULL(x)
Definition: string2.h:37
char user[128]
Username.
Definition: connaccount.h:56
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
ImapCapFlags capabilities
Capability flags.
Definition: adata.h:55
char * capstr
Capability string from the server.
Definition: adata.h:54
char * buf
Definition: adata.h:59
struct Connection * conn
Connection to IMAP server.
Definition: adata.h:41
+ Here is the call graph for this function: