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