NeoMutt  2021-10-29-43-g6b8931
Teaching an old dog new tricks
DOXYGEN
auth_sasl.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <stddef.h>
31 #include <sasl/sasl.h>
32 #include <sasl/saslutil.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include "private.h"
37 #include "mutt/lib.h"
38 #include "conn/lib.h"
39 #include "adata.h"
40 #include "auth.h"
41 #include "mutt_socket.h"
42 
46 enum ImapAuthRes imap_auth_sasl(struct ImapAccountData *adata, const char *method)
47 {
48  sasl_conn_t *saslconn = NULL;
49  sasl_interact_t *interaction = NULL;
50  int rc, irc;
51  char *buf = NULL;
52  size_t bufsize = 0;
53  const char *mech = NULL;
54  const char *pc = NULL;
55  unsigned int len = 0, olen = 0;
56  bool client_start;
57 
58  if (mutt_sasl_client_new(adata->conn, &saslconn) < 0)
59  {
60  mutt_debug(LL_DEBUG1, "Error allocating SASL connection\n");
61  return IMAP_AUTH_FAILURE;
62  }
63 
64  rc = SASL_FAIL;
65 
66  /* If the user hasn't specified a method, use any available */
67  if (!method)
68  {
69  method = adata->capstr;
70 
71  /* hack for SASL ANONYMOUS support:
72  * 1. Fetch username. If it's "" or "anonymous" then
73  * 2. attempt sasl_client_start with only "AUTH=ANONYMOUS" capability
74  * 3. if sasl_client_start fails, fall through... */
75 
76  if (mutt_account_getuser(&adata->conn->account) < 0)
77  {
78  sasl_dispose(&saslconn);
79  return IMAP_AUTH_FAILURE;
80  }
81 
82  if ((adata->capabilities & IMAP_CAP_AUTH_ANONYMOUS) &&
83  (!adata->conn->account.user[0] ||
84  mutt_str_startswith(adata->conn->account.user, "anonymous")))
85  {
86  rc = sasl_client_start(saslconn, "AUTH=ANONYMOUS", NULL, &pc, &olen, &mech);
87  }
88  }
89  else if (mutt_istr_equal("login", method) && !strstr(NONULL(adata->capstr), "AUTH=LOGIN"))
90  {
91  /* do not use SASL login for regular IMAP login */
92  sasl_dispose(&saslconn);
93  return IMAP_AUTH_UNAVAIL;
94  }
95 
96  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
97  {
98  do
99  {
100  rc = sasl_client_start(saslconn, method, &interaction, &pc, &olen, &mech);
101  if (rc == SASL_INTERACT)
102  mutt_sasl_interact(interaction);
103  } while (rc == SASL_INTERACT);
104  }
105 
106  client_start = (olen > 0);
107 
108  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
109  {
110  if (method)
111  mutt_debug(LL_DEBUG2, "%s unavailable\n", method);
112  else
113  {
114  mutt_debug(
115  LL_DEBUG1,
116  "Failure starting authentication exchange. No shared mechanisms?\n");
117  }
118  /* SASL doesn't support LOGIN, so fall back */
119 
120  sasl_dispose(&saslconn);
121  return IMAP_AUTH_UNAVAIL;
122  }
123 
124  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
125  mutt_message(_("Authenticating (%s)..."), mech);
126 
127  bufsize = MAX((olen * 2), 1024);
128  buf = mutt_mem_malloc(bufsize);
129 
130  snprintf(buf, bufsize, "AUTHENTICATE %s", mech);
131  if ((adata->capabilities & IMAP_CAP_SASL_IR) && client_start)
132  {
133  len = mutt_str_len(buf);
134  buf[len++] = ' ';
135  if (sasl_encode64(pc, olen, buf + len, bufsize - len, &olen) != SASL_OK)
136  {
137  mutt_debug(LL_DEBUG1, "#1 error base64-encoding client response\n");
138  goto bail;
139  }
140  client_start = false;
141  olen = 0;
142  }
143  imap_cmd_start(adata, buf);
144  irc = IMAP_RES_CONTINUE;
145 
146  /* looping protocol */
147  while ((rc == SASL_CONTINUE) || (olen > 0))
148  {
149  do
150  {
151  irc = imap_cmd_step(adata);
152  } while (irc == IMAP_RES_CONTINUE);
153 
154  if ((irc == IMAP_RES_BAD) || (irc == IMAP_RES_NO))
155  goto bail;
156 
157  if (irc == IMAP_RES_RESPOND)
158  {
159  /* Exchange incorrectly returns +\r\n instead of + \r\n */
160  if (adata->buf[1] == '\0')
161  {
162  buf[0] = '\0';
163  len = 0;
164  }
165  else
166  {
167  len = strlen(adata->buf + 2);
168  if (len > bufsize)
169  {
170  bufsize = len;
171  mutt_mem_realloc(&buf, bufsize);
172  }
173  /* For sasl_decode64, the fourth parameter, outmax, doesn't
174  * include space for the trailing null */
175  if (sasl_decode64(adata->buf + 2, len, buf, bufsize - 1, &len) != SASL_OK)
176  {
177  mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
178  goto bail;
179  }
180  }
181  }
182 
183  /* client-start is only available with the SASL-IR extension, but
184  * SASL 2.1 seems to want to use it regardless, at least for DIGEST
185  * fast reauth. Override if the server sent an initial continuation */
186  if (!client_start || buf[0])
187  {
188  do
189  {
190  rc = sasl_client_step(saslconn, buf, len, &interaction, &pc, &olen);
191  if (rc == SASL_INTERACT)
192  mutt_sasl_interact(interaction);
193  } while (rc == SASL_INTERACT);
194  }
195  else
196  client_start = false;
197 
198  /* send out response, or line break if none needed */
199  if (olen)
200  {
201  if ((olen * 2) > bufsize)
202  {
203  bufsize = olen * 2;
204  mutt_mem_realloc(&buf, bufsize);
205  }
206  if (sasl_encode64(pc, olen, buf, bufsize, &olen) != SASL_OK)
207  {
208  mutt_debug(LL_DEBUG1, "#2 error base64-encoding client response\n");
209  goto bail;
210  }
211  }
212 
213  if (irc == IMAP_RES_RESPOND)
214  {
215  mutt_str_copy(buf + olen, "\r\n", bufsize - olen);
216  mutt_socket_send(adata->conn, buf);
217  }
218 
219  /* If SASL has errored out, send an abort string to the server */
220  if (rc < 0)
221  {
222  mutt_socket_send(adata->conn, "*\r\n");
223  mutt_debug(LL_DEBUG1, "sasl_client_step error %d\n", rc);
224  }
225 
226  olen = 0;
227  }
228 
229  while (irc != IMAP_RES_OK)
230  {
231  irc = imap_cmd_step(adata);
232  if (irc != IMAP_RES_CONTINUE)
233  break;
234  }
235 
236  if (rc != SASL_OK)
237  goto bail;
238 
239  if (imap_code(adata->buf))
240  {
241  mutt_sasl_setup_conn(adata->conn, saslconn);
242  FREE(&buf);
243  return IMAP_AUTH_SUCCESS;
244  }
245 
246 bail:
247  sasl_dispose(&saslconn);
248  FREE(&buf);
249 
250  if (method)
251  {
252  mutt_debug(LL_DEBUG2, "%s failed\n", method);
253  return IMAP_AUTH_UNAVAIL;
254  }
255 
256  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
257  mutt_error(_("%s authentication failed"), "SASL ");
258 
259  return IMAP_AUTH_FAILURE;
260 }
IMAP authenticator multiplexor.
ImapAuthRes
Results of IMAP Authentication.
Definition: auth.h:38
@ 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
enum ImapAuthRes imap_auth_sasl(struct ImapAccountData *adata, const char *method)
Default authenticator if available - Implements ImapAuth::authenticate()
Definition: auth_sasl.c:46
Connection Library.
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:48
#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:1070
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1084
bool imap_code(const char *s)
Was the command successful.
Definition: command.c:1207
#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:40
#define MAX(a, b)
Definition: memory.h:30
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition: string.c:727
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:158
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:475
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:560
NeoMutt connections.
#define mutt_socket_send(conn, buf)
Definition: mutt_socket.h:37
Pop-specific Account data.
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:701
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:606
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:737
GUI display the mailboxes in a side panel.
#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
IMAP-specific Account data -.
Definition: adata.h:40
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