mech_gssapi.c revision 1.5
1/* $NetBSD: mech_gssapi.c,v 1.5 2011/02/12 23:21:32 christos Exp $ */
2
3/* Copyright (c) 2010 The NetBSD Foundation, Inc.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Mateusz Kocielski.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 *    must display the following acknowledgement:
19 *        This product includes software developed by the NetBSD
20 *        Foundation, Inc. and its contributors.
21 * 4. Neither the name of The NetBSD Foundation nor the names of its
22 *    contributors may be used to endorse or promote products derived
23 *    from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37#include <sys/cdefs.h>
38__RCSID("$NetBSD: mech_gssapi.c,v 1.5 2011/02/12 23:21:32 christos Exp $");
39
40#include <assert.h>
41#include <errno.h>
42#include <limits.h>	/* for LINE_MAX */
43#include <saslc.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47
48#include <gssapi/gssapi.h>
49
50#include "buffer.h"
51#include "list.h"
52#include "mech.h"
53#include "msg.h"
54#include "saslc_private.h"
55
56
57/* See RFC 2222 section 7.2.1. */
58
59/* properties */
60#define SASLC_GSSAPI_AUTHCID		SASLC_PROP_AUTHCID
61#define SASLC_GSSAPI_HOSTNAME		SASLC_PROP_HOSTNAME
62#define SASLC_GSSAPI_SERVICE		SASLC_PROP_SERVICE
63#define SASLC_GSSAPI_QOPMASK		SASLC_PROP_QOPMASK
64
65#define DEFAULT_QOP_MASK	(F_QOP_NONE | F_QOP_INT | F_QOP_CONF)
66
67/* authentication steps */
68typedef enum {	/* see RFC2222 7.2.1 section */
69	GSSAPI_AUTH_FIRST,		/* first authentication stage */
70	GSSAPI_AUTH_NEXT,		/* next authentication stage(s) */
71	GSSAPI_AUTH_LAST,		/* final authentication stage */
72	GSSAPI_AUTH_DONE		/* authenticated */
73} saslc__mech_gssapi_status_t;
74
75/* gssapi mechanism session */
76typedef struct {
77	saslc__mech_sess_t mech_sess;		/* mechanism session */
78	saslc__mech_gssapi_status_t status;	/* authentication status */
79	gss_ctx_id_t gss_ctx;			/* GSSAPI context */
80	gss_name_t server_name;			/* server name: service@host */
81	gss_name_t client_name;			/* client name - XXX: unused! */
82	uint32_t qop_mask;			/* available QOP services */
83	uint32_t omaxbuf;			/* maximum output buffer size */
84	uint32_t imaxbuf;			/* maximum input buffer size */
85	saslc__buffer32_context_t *dec_ctx;	/* decode buffer context */
86	saslc__buffer_context_t *enc_ctx;	/* encode buffer context */
87} saslc__mech_gssapi_sess_t;
88
89/**
90 * @brief creates gssapi mechanism session.
91 * Function initializes also default options for the session.
92 * @param sess sasl session
93 * @return 0 on success, -1 on failure.
94 */
95static int
96saslc__mech_gssapi_create(saslc_sess_t *sess)
97{
98	saslc__mech_gssapi_sess_t *c;
99
100	c = sess->mech_sess = calloc(1, sizeof(*c));
101	if (c == NULL)
102		return -1;
103
104	sess->mech_sess = c;
105
106	c->gss_ctx = GSS_C_NO_CONTEXT;
107	c->server_name = GSS_C_NO_NAME;
108	c->client_name = GSS_C_NO_NAME;
109
110	return 0;
111}
112
113/**
114 * @brief destroys gssapi mechanism session.
115 * Function also is freeing assigned resources to the session.
116 * @param sess sasl session
117 * @return Functions always returns 0.
118 */
119static int
120saslc__mech_gssapi_destroy(saslc_sess_t *sess)
121{
122	saslc__mech_gssapi_sess_t *ms;
123	OM_uint32 min_s;
124
125	ms = sess->mech_sess;
126
127	if (ms->gss_ctx != GSS_C_NO_CONTEXT)
128		gss_delete_sec_context(&min_s, &ms->gss_ctx, GSS_C_NO_BUFFER);
129	if (ms->server_name != GSS_C_NO_NAME)
130		gss_release_name(&min_s, &ms->server_name);
131	if (ms->client_name != GSS_C_NO_NAME)
132		gss_release_name(&min_s, &ms->client_name);
133
134	saslc__buffer_destroy(ms->enc_ctx);
135	saslc__buffer32_destroy(ms->dec_ctx);
136	free(ms);
137	sess->mech_sess = NULL;
138
139	return 0;
140}
141
142/**
143 * @brief translate the major and minor statuses an error message for
144 * the given mechanism
145 * @param maj_s major status
146 * @param min_s minor status
147 * @param mech mechanism
148 * @return pointer to a static buffer with error message
149 */
150static char *
151saslc__mech_gssapi_err(OM_uint32 maj_s, OM_uint32 min_s, gss_OID mech)
152{
153	static char errbuf[LINE_MAX];
154	gss_buffer_desc maj_error_message;
155	gss_buffer_desc min_error_message;
156	OM_uint32 disp_min_s;
157	OM_uint32 msg_ctx;
158
159	msg_ctx = 0;
160	maj_error_message.length = 0;
161	maj_error_message.value = NULL;
162	min_error_message.length = 0;
163	min_error_message.value = NULL;
164
165	(void)gss_display_status(&disp_min_s, maj_s, GSS_C_GSS_CODE,
166	    mech, &msg_ctx, &maj_error_message);
167	(void)gss_display_status(&disp_min_s, min_s, GSS_C_MECH_CODE,
168	    mech, &msg_ctx, &min_error_message);
169
170	(void)snprintf(errbuf, sizeof(errbuf),
171	    "gss-code: %lu %.*s\nmech-code: %lu %.*s",
172	    (unsigned long)maj_s,
173	    (int)maj_error_message.length,
174	    (char *)maj_error_message.value,
175	    (unsigned long)min_s,
176	    (int)min_error_message.length,
177	    (char *)min_error_message.value);
178
179	(void)gss_release_buffer(&disp_min_s, &maj_error_message);
180	(void)gss_release_buffer(&disp_min_s, &min_error_message);
181
182	return errbuf;
183}
184
185/**
186 * @brief set a session error message using saslc__mech_gssapi_err()
187 * @param sess the session
188 * @param err error number to set
189 * @param maj_s major status
190 * @param min_s minor status
191 * @return pointer to a static buffer with error message
192 */
193static void
194saslc__mech_gssapi_set_err(saslc_sess_t *sess, int err, OM_uint32 maj_s, OM_uint32 min_s)
195{
196
197	saslc__error_set(ERR(sess), err,
198	    saslc__mech_gssapi_err(maj_s, min_s, GSS_C_NO_OID));
199}
200
201/**
202 * @brief convert an initialization output token into the out and outlen format.
203 * Also releases the output token.
204 * @param sess saslc session
205 * @param outbuf gss buffer token
206 * @param out pointer to a void pointer
207 * @param outlen pointer to size_t length storage
208 * @returns 0 on success, -1 on failure
209 */
210static int
211prep_output(saslc_sess_t *sess, gss_buffer_t outbuf, void **out, size_t *outlen)
212{
213	OM_uint32 min_s;
214
215	if (outbuf == GSS_C_NO_BUFFER || outbuf->value == NULL) {
216		*outlen = 0;
217		*out = NULL;
218		return 0;
219	}
220	if (outbuf->length == 0) {
221		*outlen = 0;
222		*out = NULL;
223		gss_release_buffer(&min_s, outbuf);
224		return 0;
225	}
226	*out = malloc(outbuf->length);
227	if (*out == NULL) {
228		*outlen = 0;
229		gss_release_buffer(&min_s, outbuf);
230		saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
231		return -1;
232	}
233	*outlen = outbuf->length;
234	memcpy(*out, outbuf->value, outbuf->length);
235	gss_release_buffer(&min_s, outbuf);
236	return 0;
237}
238
239/**
240 * @brief convert an output token into a valid packet where the first
241 * 4 bytes are the payload length in network byte order.
242 * Also releases the output token.
243 * @param sess saslc session
244 * @param outbuf gss buffer token
245 * @param out pointer to a void pointer
246 * @param outlen pointer to size_t length storage
247 * @returns 0 on success, -1 on failure
248 */
249static int
250prep_packet(saslc_sess_t *sess, gss_buffer_t outbuf, void **out, size_t *outlen)
251{
252	saslc__mech_gssapi_sess_t *ms;
253	OM_uint32 min_s;
254	char *buf;
255	size_t buflen;
256
257	ms = sess->mech_sess;
258
259	if (outbuf == GSS_C_NO_BUFFER || outbuf->value == NULL) {
260		*outlen = 0;
261		*out = NULL;
262		return 0;
263	}
264	if (outbuf->length == 0) {
265		*outlen = 0;
266		*out = NULL;
267		gss_release_buffer(&min_s, outbuf);
268		return 0;
269	}
270	buflen = outbuf->length + 4;
271	if (buflen > ms->omaxbuf) {
272		saslc__error_set(ERR(sess), ERROR_MECH,
273		    "output exceeds server maxbuf size");
274		gss_release_buffer(&min_s, outbuf);
275		return -1;
276	}
277	buf = malloc(buflen);
278	if (buf == NULL) {
279		saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
280		return -1;
281	}
282	be32enc(buf, (uint32_t)outbuf->length);
283	memcpy(buf + 4, outbuf->value, outbuf->length);
284	gss_release_buffer(&min_s, outbuf);
285
286	*out = buf;
287	*outlen = buflen;
288	return 0;
289}
290
291/**
292 * @brief encodes one block of data using the negotiated security layer.
293 * @param sess sasl session
294 * @param in input data
295 * @param inlen input data length
296 * @param out place to store output data
297 * @param outlen output data length
298 * @return number of bytes consumed, zero if more needed, or -1 on failure.
299 */
300static ssize_t
301saslc__mech_gssapi_encode(saslc_sess_t *sess, const void *in, size_t inlen,
302    void **out, size_t *outlen)
303{
304	saslc__mech_gssapi_sess_t *ms;
305	gss_buffer_desc input, output;
306	OM_uint32 min_s, maj_s;
307	uint8_t *buf;
308	size_t buflen;
309	ssize_t len;
310
311	ms = sess->mech_sess;
312	assert(ms->mech_sess.qop != QOP_NONE);
313	if (ms->mech_sess.qop == QOP_NONE)
314		return -1;
315
316	len = saslc__buffer_fetch(ms->enc_ctx, in, inlen, &buf, &buflen);
317	if (len == -1)
318		return -1;
319
320	if (buflen == 0) {
321		*out = NULL;
322		*outlen = 0;
323		return len;
324	}
325
326	input.value = buf;
327	input.length = buflen;
328	output.value = NULL;
329	output.length = 0;
330
331	maj_s = gss_wrap(&min_s, ms->gss_ctx, ms->mech_sess.qop == QOP_CONF,
332	    GSS_C_QOP_DEFAULT, &input, NULL, &output);
333
334	if (GSS_ERROR(maj_s)) {
335		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
336		return -1;
337	}
338	if (prep_packet(sess, &output, out, outlen) == -1)
339		return -1;
340
341	return len;
342}
343
344/**
345 * @brief decodes one block of data using the negotiated security layer.
346 * @param sess sasl session
347 * @param in input data
348 * @param inlen input data length
349 * @param out place to store output data
350 * @param outlen output data length
351 * @return number of bytes consumed, zero if more needed, or -1 on failure.
352 */
353static ssize_t
354saslc__mech_gssapi_decode(saslc_sess_t *sess, const void *in, size_t inlen,
355	void **out, size_t *outlen)
356{
357	saslc__mech_gssapi_sess_t *ms;
358	gss_buffer_desc input, output;
359	OM_uint32 min_s, maj_s;
360	uint8_t *buf;
361	size_t buflen;
362	ssize_t len;
363
364	ms = sess->mech_sess;
365	assert(ms->mech_sess.qop != QOP_NONE);
366	if (ms->mech_sess.qop == QOP_NONE)
367		return -1;
368
369	len = saslc__buffer32_fetch(ms->dec_ctx, in, inlen, &buf, &buflen);
370	if (len == -1)
371		return -1;
372
373	if (buflen == 0) {
374		*out = NULL;
375		*outlen = 0;
376		return len;
377	}
378
379	/* buf -> szbuf (4 bytes) followed by the payload buffer */
380	input.value = buf + 4;
381	input.length = buflen - 4;
382	output.value = NULL;
383	output.length = 0;
384
385	maj_s = gss_unwrap(&min_s, ms->gss_ctx, &input, &output, NULL, NULL);
386
387	if (GSS_ERROR(maj_s)) {
388		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
389		return -1;
390	}
391
392	if (prep_output(sess, &output, out, outlen) == -1)
393		return -1;
394
395	return len;
396}
397
398/**
399 * @brief get service name from properties
400 * ("<servicename>@<hostname>") and store it in service token.
401 * @param sess the session context
402 * @param service the gs_name_t token to return service name in
403 * @return 0 on success, -1 on error
404 */
405static int
406get_service(saslc_sess_t *sess, gss_name_t *service)
407{
408	gss_buffer_desc bufdesc;
409	const char *hostname, *servicename;
410	char *buf;
411	int buflen;
412	OM_uint32 min_s, maj_s;
413
414	hostname = saslc_sess_getprop(sess, SASLC_GSSAPI_HOSTNAME);
415	if (hostname == NULL) {
416		saslc__error_set(ERR(sess), ERROR_MECH,
417		    "hostname is required for an authentication");
418		return -1;
419	}
420	servicename = saslc_sess_getprop(sess, SASLC_GSSAPI_SERVICE);
421	if (servicename == NULL) {
422		saslc__error_set(ERR(sess), ERROR_MECH,
423		    "service is required for an authentication");
424		return -1;
425	}
426	buflen = asprintf(&buf, "%s@%s", servicename, hostname);
427	if (buflen == -1) {
428		saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
429		return -1;
430	}
431	bufdesc.value = buf;
432	bufdesc.length = buflen + 1;
433
434	saslc__msg_dbg("%s: buf='%s'", __func__, buf);
435
436	maj_s = gss_import_name(&min_s, &bufdesc, GSS_C_NT_HOSTBASED_SERVICE,
437	    service);
438	free(buf);
439	if (GSS_ERROR(maj_s)) {
440		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
441		return -1;
442	}
443	return 0;
444}
445
446/**
447 * @brief gss_init_sec_context() wrapper
448 * @param sess session context
449 * @param inbuf input token
450 * @param outbuf output token
451 * @return 0 if GSS_S_COMPLETE, 1 if GSS_S_CONTINUE_NEEDED, -1 on failure
452 */
453static int
454init_sec_context(saslc_sess_t *sess, gss_buffer_t inbuf, gss_buffer_t outbuf)
455{
456	saslc__mech_gssapi_sess_t *ms;
457	OM_uint32 min_s, maj_s;
458
459	ms = sess->mech_sess;
460
461	outbuf->length = 0;
462	outbuf->value = NULL;
463	maj_s = gss_init_sec_context(
464		&min_s,			/* minor status */
465		GSS_C_NO_CREDENTIAL, /* use current login context credential */
466		&ms->gss_ctx,		/* initially GSS_C_NO_CONTEXT */
467		ms->server_name,	/* server@hostname */
468		GSS_C_NO_OID,		/* use default mechanism */
469#if 1
470		GSS_C_REPLAY_FLAG |	/* message replay detection */
471		GSS_C_INTEG_FLAG |	/* request integrity */
472		GSS_C_CONF_FLAG |	/* request confirmation */
473#endif
474		GSS_C_MUTUAL_FLAG |	/* mutual authentication */
475		GSS_C_SEQUENCE_FLAG,	/* message sequence checking */
476		0,			/* default lifetime (2 hrs) */
477		GSS_C_NO_CHANNEL_BINDINGS,
478		inbuf,			/* input token */
479		/* output parameters follow */
480		NULL,			/* mechanism type for context */
481		outbuf,			/* output token */
482		NULL,			/* services available for context */
483		NULL);			/* lifetime of context */
484
485	switch (maj_s) {
486	case GSS_S_COMPLETE:
487		return 0;
488	case GSS_S_CONTINUE_NEEDED:
489		return 1;
490	default:
491		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
492		return -1;
493	}
494}
495
496/**
497 * @brief unwrap the authentication token received from the server.
498 * This contains the qop_mask and maxbuf values which are updated in
499 * saslc__mech_gssapi_sess_t.
500 * @param sess the session context
501 * @param inbuf the received authentication token.
502 * @return 0 on success, -1 on error.
503 */
504static int
505unwrap_input_token(saslc_sess_t *sess, gss_buffer_t inbuf)
506{
507	saslc__mech_gssapi_sess_t *ms;
508	OM_uint32 min_s, maj_s;
509	gss_buffer_t outbuf;
510	gss_buffer_desc outdesc;
511	unsigned char *p;
512
513	/********************************************************************/
514	/* [RFC 2222 section 7.2.1]                                         */
515	/* The client passes this token to GSS_Unwrap and interprets        */
516	/* the first octet of resulting cleartext as a bit-mask specifying  */
517	/* the security layers supported by the server and the second       */
518	/* through fourth octets as the maximum size output_message to send */
519	/* to the server.                                                   */
520	/********************************************************************/
521
522	ms = sess->mech_sess;
523
524	outbuf = &outdesc;
525	maj_s = gss_unwrap(&min_s, ms->gss_ctx, inbuf, outbuf, NULL, NULL);
526
527	if (GSS_ERROR(maj_s)) {
528		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
529		return -1;
530	}
531	if (outbuf->length != 4) {
532		saslc__error_set(ERR(sess), ERROR_MECH,
533		    "invalid unwrap length");
534		return -1;
535	}
536	p = outbuf->value;
537	ms->qop_mask = p[0];
538	ms->omaxbuf = (be32dec(p) & 0xffffff);
539
540	saslc__msg_dbg("%s: qop_mask=0x%02x omaxbuf=%d",
541	    __func__, ms->qop_mask, ms->omaxbuf);
542
543	if (ms->qop_mask == QOP_NONE && ms->omaxbuf != 0) {
544		saslc__error_set(ERR(sess), ERROR_MECH,
545		    "server has no security layer support, but maxbuf != 0");
546		return -1;
547	}
548	maj_s = gss_release_buffer(&min_s, outbuf);
549	if (GSS_ERROR(maj_s)) {
550		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
551		return -1;
552	}
553	return 0;
554}
555
556/**
557 * @brief construct and wrap up an authentication token and put it in
558 * outbuf.  The outbuf token data is structured as follows:
559 * struct {
560 *   uint8_t qop;	// qop to use
561 *   uint8_t maxbuf[3]	// maxbuf for client (network byte order)
562 *   uint8_t authcid[]	// variable length authentication id (username)
563 * } __packed;
564 * @param sess the session
565 * @param outbuf the gss_buffer_t token to return to server.
566 * @return 0 on success, -1 on error.
567 */
568static int
569wrap_output_token(saslc_sess_t *sess, gss_buffer_t outbuf)
570{
571	saslc__mech_gssapi_sess_t *ms;
572	gss_buffer_desc indesc;
573	char *input_value;
574	int len;
575	const char *authcid;
576	OM_uint32 min_s, maj_s;
577	unsigned char *p;
578
579	/********************************************************************/
580	/* [RFC 2222 section 7.2.1]                                         */
581	/* The client then constructs data, with the first octet containing */
582	/* the bit-mask specifying the selected security layer, the second  */
583	/* through fourth octets containing in network byte order the       */
584	/* maximum size output_message the client is able to receive, and   */
585	/* the remaining octets containing the authorization identity.  The */
586	/* client passes the data to GSS_Wrap with conf_flag set to FALSE,  */
587	/* and responds with the generated output_message.  The client can  */
588	/* then consider the server authenticated.                          */
589	/********************************************************************/
590
591	ms = sess->mech_sess;
592
593	if ((authcid = saslc_sess_getprop(sess, SASLC_GSSAPI_AUTHCID))
594	    == NULL) {
595		saslc__error_set(ERR(sess), ERROR_MECH,
596		    "authcid is required for an authentication");
597		return -1;
598	}
599
600	len = asprintf(&input_value, "qmax%s", authcid);
601	if (len == -1) {
602		saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
603		return -1;
604	}
605	be32enc(input_value, ms->imaxbuf);
606	input_value[0] = saslc__mech_qop_flag(ms->mech_sess.qop);
607
608	indesc.value = input_value;
609	indesc.length = len;	/* XXX: don't count the '\0' */
610
611	p = (unsigned char *)input_value;
612	saslc__msg_dbg("%s: input_value='%02x %02x %02x %02x %s",
613	    __func__, p[0], p[1], p[2], p[3], input_value + 4);
614
615	maj_s = gss_wrap(&min_s, ms->gss_ctx, 0 /* FALSE - RFC2222 */,
616	    GSS_C_QOP_DEFAULT, &indesc, NULL, outbuf);
617
618	free(input_value);
619
620	if (GSS_ERROR(maj_s)) {
621		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
622		return -1;
623	}
624	return 0;
625}
626
627/************************************************************************
628 * XXX: Share this with mech_digestmd5.c?  They are almost identical.
629 */
630/**
631 * @brief choose the best qop based on what was provided by the
632 * challenge and a possible user mask.
633 * @param sess the session context
634 * @param qop_flags the qop flags parsed from the challenge string
635 * @return the selected saslc__mech_sess_qop_t or -1 if no match
636 */
637static int
638choose_qop(saslc_sess_t *sess, uint32_t qop_flags)
639{
640	list_t *list;
641	const char *user_qop;
642
643	qop_flags &= DEFAULT_QOP_MASK;
644	user_qop = saslc_sess_getprop(sess, SASLC_GSSAPI_QOPMASK);
645	if (user_qop != NULL) {
646		list = saslc__list_parse(user_qop);
647		qop_flags &= saslc__mech_qop_list_flags(list);
648		saslc__list_free(list);
649	}
650
651	/*
652	 * Select the most secure supported qop.
653	 */
654	if ((qop_flags & F_QOP_CONF) != 0)
655		return QOP_CONF;
656	if ((qop_flags & F_QOP_INT) != 0)
657		return QOP_INT;
658	if ((qop_flags & F_QOP_NONE) != 0)
659		return QOP_NONE;
660
661	saslc__error_set(ERR(sess), ERROR_MECH,
662	    "cannot choose an acceptable qop");
663	return -1;
664}
665/************************************************************************/
666
667/**
668 * @brief compute the maximum buffer length we can use and not
669 * overflow the servers maxbuf.
670 * @param sess the session context
671 * @param maxbuf the server's maxbuf value
672 */
673static int
674wrap_size_limit(saslc_sess_t *sess, OM_uint32 maxbuf)
675{
676	saslc__mech_gssapi_sess_t *ms;
677	OM_uint32 min_s, maj_s;
678	OM_uint32 max_input;
679
680	ms = sess->mech_sess;
681
682	maj_s = gss_wrap_size_limit(&min_s, ms->gss_ctx, 1, GSS_C_QOP_DEFAULT,
683	    maxbuf, &max_input);
684
685	if (GSS_ERROR(maj_s)) {
686		saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s);
687		return -1;
688	}
689
690	/* XXX: from cyrus-sasl: gssapi.c */
691	if (max_input > maxbuf) {
692		/* Heimdal appears to get this wrong */
693		maxbuf -= (max_input - maxbuf);
694	} else {
695		/* This code is actually correct */
696		maxbuf = max_input;
697	}
698	return maxbuf;
699}
700
701/**
702 * @brief set our imaxbuf (from omaxbuf or from properties) and
703 * then reset omaxbuf in saslc__mech_gssapi_sess_t.
704 * @param sess the session context
705 * @return 0 on success, -1 on error
706 *
707 * Note: on entry the omaxbuf is the server's maxbuf size.  On exit
708 * the omaxbuf is the maximum buffer we can fill that will not
709 * overflow the servers maxbuf after it is encoded.  This value is
710 * given by wrap_size_limit().
711 */
712static int
713set_maxbufs(saslc_sess_t *sess)
714{
715	saslc__mech_gssapi_sess_t *ms;
716	const char *p;
717	char *q;
718	unsigned long val;
719	int rv;
720
721	ms = sess->mech_sess;
722
723	/* by default, we use the same input maxbuf as the server. */
724	ms->imaxbuf = ms->omaxbuf;
725	p = saslc_sess_getprop(sess, SASLC_PROP_MAXBUF);
726	if (p != NULL) {
727		val = strtol(p, &q, 0);
728		if (p[0] == '\0' || *q != '\0') {
729
730			return MECH_ERROR;
731		}
732		if (errno == ERANGE && val == ULONG_MAX) {
733
734			return MECH_ERROR;
735		}
736		if (val > 0xffffff)
737			val = 0xffffff;
738		ms->imaxbuf = (uint32_t)val;
739	}
740	rv = wrap_size_limit(sess, ms->omaxbuf);
741	if (rv == -1)
742		return MECH_ERROR;
743	ms->omaxbuf = rv;	/* maxbuf size for unencoded output data */
744
745	return 0;
746}
747
748/**
749 * @brief do one step of the sasl authentication
750 * @param sess sasl session
751 * @param in input data
752 * @param inlen input data length
753 * @param out place to store output data
754 * @param outlen output data length
755 * @return MECH_OK on success, MECH_STEP if more steps are needed,
756 * MECH_ERROR on failure
757 */
758static int
759saslc__mech_gssapi_cont(saslc_sess_t *sess, const void *in, size_t inlen,
760    void **out, size_t *outlen)
761{
762	saslc__mech_gssapi_sess_t *ms;
763	gss_buffer_desc input, output;
764	int rv;
765
766    /**************************************************************************/
767    /* [RFC 2222 section 7.2.1]                                               */
768    /* The client calls GSS_Init_sec_context, passing in 0 for                */
769    /* input_context_handle (initially) and a targ_name equal to output_name  */
770    /* from GSS_Import_Name called with input_name_type of                    */
771    /* GSS_C_NT_HOSTBASED_SERVICE and input_name_string of                    */
772    /* "service@hostname" where "service" is the service name specified in    */
773    /* the protocol's profile, and "hostname" is the fully qualified host     */
774    /* name of the server.  The client then responds with the resulting       */
775    /* output_token.  If GSS_Init_sec_context returns GSS_S_CONTINUE_NEEDED,  */
776    /* then the client should expect the server to issue a token in a         */
777    /* subsequent challenge.  The client must pass the token to another call  */
778    /* to GSS_Init_sec_context, repeating the actions in this paragraph.      */
779    /*                                                                        */
780    /* When GSS_Init_sec_context returns GSS_S_COMPLETE, the client takes     */
781    /* the following actions: If the last call to GSS_Init_sec_context        */
782    /* returned an output_token, then the client responds with the            */
783    /* output_token, otherwise the client responds with no data.  The client  */
784    /* should then expect the server to issue a token in a subsequent         */
785    /* challenge.  The client passes this token to GSS_Unwrap and interprets  */
786    /* the first octet of resulting cleartext as a bit-mask specifying the    */
787    /* security layers supported by the server and the second through fourth  */
788    /* octets as the maximum size output_message to send to the server.  The  */
789    /* client then constructs data, with the first octet containing the       */
790    /* bit-mask specifying the selected security layer, the second through    */
791    /* fourth octets containing in network byte order the maximum size        */
792    /* output_message the client is able to receive, and the remaining        */
793    /* octets containing the authorization identity.  The client passes the   */
794    /* data to GSS_Wrap with conf_flag set to FALSE, and responds with the    */
795    /* generated output_message.  The client can then consider the server     */
796    /* authenticated.                                                         */
797    /**************************************************************************/
798
799	ms = sess->mech_sess;
800
801	switch(ms->status) {
802	case GSSAPI_AUTH_FIRST:
803		saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_FIRST");
804
805		if (get_service(sess, &ms->server_name) == -1)
806			return MECH_ERROR;
807
808		rv = init_sec_context(sess, GSS_C_NO_BUFFER, &output);
809		if (rv == -1)
810			return MECH_ERROR;
811
812		if (prep_output(sess, &output, out, outlen) == -1)
813			return MECH_ERROR;
814
815		ms->status = rv == 0 ? GSSAPI_AUTH_LAST : GSSAPI_AUTH_NEXT;
816		return MECH_STEP;
817
818	case GSSAPI_AUTH_NEXT:
819		saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_NEXT");
820
821		input.value = __UNCONST(in);
822		input.length = inlen;
823		if ((rv = init_sec_context(sess, &input, &output)) == -1)
824			return MECH_ERROR;
825
826		if (prep_output(sess, &output, out, outlen) == -1)
827			return MECH_ERROR;
828
829		if (rv == 0)
830			ms->status = GSSAPI_AUTH_LAST;
831		return MECH_STEP;
832
833	case GSSAPI_AUTH_LAST:
834		saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_LAST");
835
836		input.value = __UNCONST(in);
837		input.length = inlen;
838		if (unwrap_input_token(sess, &input) == -1)
839			return MECH_ERROR;
840
841		if ((rv = choose_qop(sess, ms->qop_mask)) == -1)
842			return MECH_ERROR;
843
844		ms->mech_sess.qop = rv;
845
846		if (ms->mech_sess.qop != QOP_NONE) {
847			if (ms->mech_sess.qop == QOP_CONF) {
848				/*
849				 * XXX: where do we negotiate the cipher,
850				 *  or do we?
851				 */
852			}
853			if (set_maxbufs(sess) == -1)
854				return MECH_ERROR;
855			ms->dec_ctx = saslc__buffer32_create(sess, ms->imaxbuf);
856			ms->enc_ctx = saslc__buffer_create(sess, ms->omaxbuf);
857		}
858		if (wrap_output_token(sess, &output) == -1)
859			return MECH_ERROR;
860
861		if (prep_output(sess, &output, out, outlen) == -1)
862			return MECH_ERROR;
863
864		ms->status = GSSAPI_AUTH_DONE;
865		return MECH_OK;
866
867	case GSSAPI_AUTH_DONE:
868		assert(/*CONSTCOND*/0);	/* XXX: impossible */
869		saslc__error_set(ERR(sess), ERROR_MECH,
870		    "already authenticated");
871		return MECH_ERROR;
872
873#if 0	/* no default so the compiler can tell us if we miss an enum */
874	default:
875		assert(/*CONSTCOND*/0); /* impossible */
876		/*NOTREACHED*/
877#endif
878	}
879	/*LINTED*/
880	assert(/*CONSTCOND*/0);		/* XXX: impossible */
881	return MECH_ERROR;
882}
883
884/* mechanism definition */
885const saslc__mech_t saslc__mech_gssapi = {
886	.name	 = "GSSAPI",
887	.flags	 = FLAG_NONE,
888	.create	 = saslc__mech_gssapi_create,
889	.cont	 = saslc__mech_gssapi_cont,
890	.encode	 = saslc__mech_gssapi_encode,
891	.decode	 = saslc__mech_gssapi_decode,
892	.destroy = saslc__mech_gssapi_destroy
893};
894