NeoMutt  2025-12-11-1009-ga75d9e
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
auth_gss.c
Go to the documentation of this file.
1
24
37
38#include "config.h"
39#include <arpa/inet.h>
40#include <string.h>
41#include "private.h"
42#include "mutt/lib.h"
43#include "config/lib.h"
44#include "core/lib.h"
45#include "conn/lib.h"
46#include "adata.h"
47#include "auth.h"
48#ifdef HAVE_HEIMDAL
49#include <gssapi/gssapi.h>
50#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
51#else
52#include <gssapi/gssapi.h>
53#include <gssapi/gssapi_generic.h>
54#endif
55
56#define GSS_AUTH_P_NONE 1
57#define GSS_AUTH_P_INTEGRITY 2
58#define GSS_AUTH_P_PRIVACY 4
59
65static void print_gss_error(OM_uint32 err_maj, OM_uint32 err_min)
66{
67 OM_uint32 maj_stat = 0;
68 OM_uint32 min_stat = 0;
69 OM_uint32 msg_ctx = 0;
70 gss_buffer_desc status_string = { 0 };
71 char buf_maj[512] = { 0 };
72 char buf_min[512] = { 0 };
73
74 do
75 {
76 maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE,
77 GSS_C_NO_OID, &msg_ctx, &status_string);
78 if (GSS_ERROR(maj_stat))
79 break;
80 size_t status_len = status_string.length;
81 if (status_len >= sizeof(buf_maj))
82 status_len = sizeof(buf_maj) - 1;
83 strncpy(buf_maj, (char *) status_string.value, status_len);
84 buf_maj[status_len] = '\0';
85 gss_release_buffer(&min_stat, &status_string);
86
87 maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE,
88 GSS_C_NULL_OID, &msg_ctx, &status_string);
89 if (!GSS_ERROR(maj_stat))
90 {
91 status_len = status_string.length;
92 if (status_len >= sizeof(buf_min))
93 status_len = sizeof(buf_min) - 1;
94 strncpy(buf_min, (char *) status_string.value, status_len);
95 buf_min[status_len] = '\0';
96 gss_release_buffer(&min_stat, &status_string);
97 }
98 } while (!GSS_ERROR(maj_stat) && (msg_ctx != 0));
99
100 mutt_debug(LL_DEBUG2, "((%s:%d )(%s:%d))\n", buf_maj, err_maj, buf_min, err_min);
101}
102
106enum ImapAuthRes imap_auth_gss(struct ImapAccountData *adata, const char *method)
107{
108 gss_buffer_desc request_buf = { 0 };
109 gss_buffer_desc send_token = { 0 };
110 gss_buffer_t sec_token = NULL;
111 gss_name_t target_name = NULL;
112 gss_ctx_id_t context = NULL;
113 gss_OID mech_name = NULL;
114 char server_conf_flags;
115 gss_qop_t quality = 0;
116 int cflags = 0;
117 OM_uint32 maj_stat = 0;
118 OM_uint32 min_stat = 0;
119 unsigned long buf_size = 0;
120 int rc2;
121 int rc = IMAP_AUTH_FAILURE;
122
123 if (!(adata->capabilities & IMAP_CAP_AUTH_GSSAPI))
124 return IMAP_AUTH_UNAVAIL;
125
126 if (mutt_account_getuser(&adata->conn->account) < 0)
127 return IMAP_AUTH_FAILURE;
128
129 struct Buffer *buf1 = buf_pool_get();
130 struct Buffer *buf2 = buf_pool_get();
131
132 /* get an IMAP service ticket for the server */
133 buf_printf(buf1, "imap@%s", adata->conn->account.host);
134 request_buf.value = buf1->data;
135 request_buf.length = buf_len(buf1);
136
137 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
138 maj_stat = gss_import_name(&min_stat, &request_buf, gss_nt_service_name, &target_name);
139 if (maj_stat != GSS_S_COMPLETE)
140 {
141 mutt_debug(LL_DEBUG2, "Couldn't get service name for [%s]\n", buf1->data);
143 goto cleanup;
144 }
145 else if (c_debug_level >= 2)
146 {
147 gss_display_name(&min_stat, target_name, &request_buf, &mech_name);
148 mutt_debug(LL_DEBUG2, "Using service name [%s]\n", (char *) request_buf.value);
149 gss_release_buffer(&min_stat, &request_buf);
150 }
151 /* Acquire initial credentials - without a TGT GSSAPI is UNAVAIL */
152 sec_token = GSS_C_NO_BUFFER;
153 context = GSS_C_NO_CONTEXT;
154
155 /* build token */
156 maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name,
157 GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG,
158 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL,
159 &send_token, (unsigned int *) &cflags, NULL);
160 if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED))
161 {
162 print_gss_error(maj_stat, min_stat);
163 mutt_debug(LL_DEBUG1, "Error acquiring credentials - no TGT?\n");
164 gss_release_name(&min_stat, &target_name);
165
167 goto cleanup;
168 }
169
170 /* now begin login */
171 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
172 mutt_message(_("Authenticating (%s)..."), "GSSAPI");
173
174 imap_cmd_start(adata, "AUTHENTICATE GSSAPI");
175
176 /* expect a null continuation response ("+") */
177 do
178 {
179 rc2 = imap_cmd_step(adata);
180 } while (rc2 == IMAP_RES_CONTINUE);
181
182 if (rc2 != IMAP_RES_RESPOND)
183 {
184 mutt_debug(LL_DEBUG2, "Invalid response from server: %s\n", buf1->data);
185 gss_release_name(&min_stat, &target_name);
186 goto bail;
187 }
188
189 /* now start the security context initialisation loop... */
190 mutt_debug(LL_DEBUG2, "Sending credentials\n");
191 mutt_b64_buffer_encode(buf1, send_token.value, send_token.length);
192 gss_release_buffer(&min_stat, &send_token);
193 buf_addstr(buf1, "\r\n");
194 mutt_socket_send(adata->conn, buf_string(buf1));
195
196 while (maj_stat == GSS_S_CONTINUE_NEEDED)
197 {
198 /* Read server data */
199 do
200 {
201 rc2 = imap_cmd_step(adata);
202 } while (rc2 == IMAP_RES_CONTINUE);
203
204 if (rc2 != IMAP_RES_RESPOND)
205 {
206 mutt_debug(LL_DEBUG1, "#1 Error receiving server response\n");
207 gss_release_name(&min_stat, &target_name);
208 goto bail;
209 }
210
211 if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0)
212 {
213 mutt_debug(LL_DEBUG1, "Invalid base64 server response\n");
214 gss_release_name(&min_stat, &target_name);
215 goto err_abort_cmd;
216 }
217 request_buf.value = buf2->data;
218 request_buf.length = buf_len(buf2);
219 sec_token = &request_buf;
220
221 /* Write client data */
222 maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name,
223 GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG,
224 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL,
225 &send_token, (unsigned int *) &cflags, NULL);
226 if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED))
227 {
228 print_gss_error(maj_stat, min_stat);
229 mutt_debug(LL_DEBUG1, "Error exchanging credentials\n");
230 gss_release_name(&min_stat, &target_name);
231
232 goto err_abort_cmd;
233 }
234 mutt_b64_buffer_encode(buf1, send_token.value, send_token.length);
235 gss_release_buffer(&min_stat, &send_token);
236 buf_addstr(buf1, "\r\n");
237 mutt_socket_send(adata->conn, buf_string(buf1));
238 }
239
240 gss_release_name(&min_stat, &target_name);
241
242 /* get security flags and buffer size */
243 do
244 {
245 rc2 = imap_cmd_step(adata);
246 } while (rc2 == IMAP_RES_CONTINUE);
247
248 if (rc2 != IMAP_RES_RESPOND)
249 {
250 mutt_debug(LL_DEBUG1, "#2 Error receiving server response\n");
251 goto bail;
252 }
253 if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0)
254 {
255 mutt_debug(LL_DEBUG1, "Invalid base64 server response\n");
256 goto err_abort_cmd;
257 }
258 request_buf.value = buf2->data;
259 request_buf.length = buf_len(buf2);
260
261 maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token, &cflags, &quality);
262 if (maj_stat != GSS_S_COMPLETE)
263 {
264 print_gss_error(maj_stat, min_stat);
265 mutt_debug(LL_DEBUG2, "Couldn't unwrap security level data\n");
266 gss_release_buffer(&min_stat, &send_token);
267 goto err_abort_cmd;
268 }
269 mutt_debug(LL_DEBUG2, "Credential exchange complete\n");
270
271 /* The unwrapped token must be at least 4 bytes: 1 byte of security flags
272 * followed by 3 bytes of maximum buffer size. Reject tokens that are
273 * too short to prevent an out-of-bounds read. */
274 if (send_token.length < 4)
275 {
276 mutt_debug(LL_DEBUG2, "Unwrapped security token too short (%zu bytes)\n",
277 send_token.length);
278 gss_release_buffer(&min_stat, &send_token);
279 goto err_abort_cmd;
280 }
281
282 /* first byte is security levels supported. We want NONE */
283 server_conf_flags = ((char *) send_token.value)[0];
284 if (!(((char *) send_token.value)[0] & GSS_AUTH_P_NONE))
285 {
286 mutt_debug(LL_DEBUG2, "Server requires integrity or privacy\n");
287 gss_release_buffer(&min_stat, &send_token);
288 goto err_abort_cmd;
289 }
290
291 /* we don't care about buffer size if we don't wrap content. But here it is */
292 ((char *) send_token.value)[0] = '\0';
293 buf_size = ntohl(*((uint32_t *) send_token.value));
294 gss_release_buffer(&min_stat, &send_token);
295 mutt_debug(LL_DEBUG2, "Unwrapped security level flags: %c%c%c\n",
296 (server_conf_flags & GSS_AUTH_P_NONE) ? 'N' : '-',
297 (server_conf_flags & GSS_AUTH_P_INTEGRITY) ? 'I' : '-',
298 (server_conf_flags & GSS_AUTH_P_PRIVACY) ? 'P' : '-');
299 mutt_debug(LL_DEBUG2, "Maximum GSS token size is %ld\n", buf_size);
300
301 /* agree to terms (hack!) */
302 buf_size = htonl(buf_size); /* not relevant without integrity/privacy */
303 buf_reset(buf1);
305 buf_addstr_n(buf1, ((char *) &buf_size) + 1, 3);
306 /* server decides if principal can log in as user */
307 buf_addstr(buf1, adata->conn->account.user);
308 request_buf.value = buf1->data;
309 request_buf.length = buf_len(buf1);
310 maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf,
311 &cflags, &send_token);
312 if (maj_stat != GSS_S_COMPLETE)
313 {
314 mutt_debug(LL_DEBUG2, "Error creating login request\n");
315 goto err_abort_cmd;
316 }
317
318 mutt_b64_buffer_encode(buf1, send_token.value, send_token.length);
319 mutt_debug(LL_DEBUG2, "Requesting authorisation as %s\n", adata->conn->account.user);
320 buf_addstr(buf1, "\r\n");
321 mutt_socket_send(adata->conn, buf_string(buf1));
322
323 /* Joy of victory or agony of defeat? */
324 do
325 {
326 rc2 = imap_cmd_step(adata);
327 } while (rc2 == IMAP_RES_CONTINUE);
328 if (rc2 == IMAP_RES_RESPOND)
329 {
330 mutt_debug(LL_DEBUG1, "Unexpected server continuation request\n");
331 goto err_abort_cmd;
332 }
333 if (imap_code(adata->buf))
334 {
335 /* flush the security context */
336 mutt_debug(LL_DEBUG2, "Releasing GSS credentials\n");
337 maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token);
338 if (maj_stat != GSS_S_COMPLETE)
339 mutt_debug(LL_DEBUG1, "Error releasing credentials\n");
340
341 /* send_token may contain a notification to the server to flush
342 * credentials. RFC1731 doesn't specify what to do, and since this
343 * support is only for authentication, we'll assume the server knows
344 * enough to flush its own credentials */
345 gss_release_buffer(&min_stat, &send_token);
346
348 goto cleanup;
349 }
350 else
351 {
352 goto bail;
353 }
354
355err_abort_cmd:
356 mutt_socket_send(adata->conn, "*\r\n");
357 do
358 {
359 rc2 = imap_cmd_step(adata);
360 } while (rc2 == IMAP_RES_CONTINUE);
361
362bail:
363 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
364 mutt_error(_("%s authentication failed"), "GSSAPI");
366
367cleanup:
368 buf_pool_release(&buf1);
369 buf_pool_release(&buf2);
370
371 return rc;
372}
IMAP authenticator multiplexor.
ImapAuthRes
Results of IMAP Authentication.
Definition auth.h:39
@ IMAP_AUTH_FAILURE
Authentication failed.
Definition auth.h:41
@ IMAP_AUTH_SUCCESS
Authentication successful.
Definition auth.h:40
@ IMAP_AUTH_UNAVAIL
Authentication method not permitted.
Definition auth.h:42
#define GSS_AUTH_P_NONE
No protection (authentication only).
Definition auth_gss.c:56
static void print_gss_error(OM_uint32 err_maj, OM_uint32 err_min)
Print detailed error message to the debug log.
Definition auth_gss.c:65
#define GSS_AUTH_P_PRIVACY
Privacy protection (encryption).
Definition auth_gss.c:58
#define GSS_AUTH_P_INTEGRITY
Integrity protection.
Definition auth_gss.c:57
size_t mutt_b64_buffer_encode(struct Buffer *buf, const char *in, size_t len)
Convert raw bytes to NUL-terminated base64 string.
Definition base64.c:243
int mutt_b64_buffer_decode(struct Buffer *buf, const char *in)
Convert NUL-terminated base64 string to raw bytes.
Definition base64.c:261
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:168
size_t buf_addstr_n(struct Buffer *buf, const char *s, size_t len)
Add a string to a Buffer, expanding it if necessary.
Definition buffer.c:109
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition buffer.c:497
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:89
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:248
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:233
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
Convenience wrapper for the config headers.
Connection Library.
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition connaccount.c:51
Convenience wrapper for the core headers.
enum ImapAuthRes imap_auth_gss(struct ImapAccountData *adata, const char *method)
GSS Authentication support - Implements ImapAuth::authenticate() -.
Definition auth_gss.c:106
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
Imap-specific Account data.
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition command.c:1216
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition command.c:1230
bool imap_code(const char *s)
Was the command successful.
Definition command.c:1372
Shared constants/structs that are private to IMAP.
#define IMAP_RES_RESPOND
+
Definition private.h:56
@ IMAP_CAP_AUTH_GSSAPI
RFC1731: GSSAPI authentication.
Definition private.h:141
#define IMAP_RES_CONTINUE
* ...
Definition private.h:55
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
#define mutt_socket_send(conn, buf)
Definition socket.h:56
String manipulation buffer.
Definition buffer.h:36
char * data
Pointer to data.
Definition buffer.h:37
char user[128]
Username.
Definition connaccount.h:62
char host[128]
Server to login to.
Definition connaccount.h:60
struct ConnAccount account
Account details: username, password, etc.
Definition connection.h:49
IMAP-specific Account data -.
Definition adata.h:40
ImapCapFlags capabilities
Capability flags.
Definition adata.h:56
char * buf
Command buffer.
Definition adata.h:61
struct Connection * conn
Connection to IMAP server.
Definition adata.h:41
Container for Accounts, Notifications.
Definition neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49