GSS_S_PROMPTING_NEEDED is a bit
[cyrus-sasl.git] / plugins / gs2.c
1 /*
2  * Copyright (c) 2010, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 /*
33  * Copyright (c) 1998-2003 Carnegie Mellon University.
34  * All rights reserved.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  *
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  *
43  * 2. Redistributions in binary form must reproduce the above copyright
44  *    notice, this list of conditions and the following disclaimer in
45  *    the documentation and/or other materials provided with the
46  *    distribution.
47  *
48  * 3. The name "Carnegie Mellon University" must not be used to
49  *    endorse or promote products derived from this software without
50  *    prior written permission. For permission or any other legal
51  *    details, please contact
52  *      Office of Technology Transfer
53  *      Carnegie Mellon University
54  *      5000 Forbes Avenue
55  *      Pittsburgh, PA  15213-3890
56  *      (412) 268-4387, fax: (412) 268-7395
57  *      tech-transfer@andrew.cmu.edu
58  *
59  * 4. Redistributions of any form whatsoever must retain the following
60  *    acknowledgment:
61  *    "This product includes software developed by Computing Services
62  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
63  *
64  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
65  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
66  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
67  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
68  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
69  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
70  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
71  */
72
73 #include <config.h>
74 #include <gssapi/gssapi.h>
75 #ifdef HAVE_GSSAPI_GSSAPI_EXT_H
76 #include <gssapi/gssapi_ext.h>
77 #endif
78 #include <fcntl.h>
79 #include <stdio.h>
80 #include <sasl.h>
81 #include <saslutil.h>
82 #include <saslplug.h>
83
84 #include "plugin_common.h"
85
86 #ifdef HAVE_UNISTD_H
87 #include <unistd.h>
88 #endif
89
90 #include <errno.h>
91 #include <assert.h>
92 #include "gs2_token.h"
93
94 #define GS2_CB_FLAG_MASK    0x0F
95 #define GS2_CB_FLAG_N       0x00
96 #define GS2_CB_FLAG_P       0x01
97 #define GS2_CB_FLAG_Y       0x02
98 #define GS2_NONSTD_FLAG     0x10
99
100 #ifndef GSS_S_PROMPTING_NEEDED
101 #define GSS_S_PROMPTING_NEEDED (1 << (GSS_C_SUPPLEMENTARY_OFFSET + 5))
102 #endif
103
104 typedef struct context {
105     gss_ctx_id_t gss_ctx;
106     gss_name_t client_name;
107     gss_name_t server_name;
108     gss_cred_id_t server_creds;
109     gss_cred_id_t client_creds;
110     char *out_buf;
111     unsigned out_buf_len;
112     const sasl_utils_t *utils;
113     char *authid;
114     char *authzid;
115     union {
116         sasl_client_plug_t *client;
117         sasl_server_plug_t *server;
118     } plug;
119     gss_OID mechanism;
120     int gs2_flags;
121     char *cbindingname;
122     struct gss_channel_bindings_struct gss_cbindings;
123     sasl_secret_t *password;
124     unsigned int free_password;
125     OM_uint32 lifetime;
126 } context_t;
127
128 static gss_OID_set gs2_mechs = GSS_C_NO_OID_SET;
129
130 static int gs2_get_init_creds(context_t *context,
131                               sasl_client_params_t *params,
132                               sasl_interact_t **prompt_need,
133                               sasl_out_params_t *oparams);
134
135 static int gs2_verify_initial_message(context_t *text,
136                                       sasl_server_params_t *sparams,
137                                       const char *in,
138                                       unsigned inlen,
139                                       gss_buffer_t token);
140
141 static int gs2_make_header(context_t *text,
142                            sasl_client_params_t *cparams,
143                            const char *authzid,
144                            char **out,
145                            unsigned *outlen);
146
147 static int gs2_make_message(context_t *text,
148                             sasl_client_params_t *cparams,
149                             int initialContextToken,
150                             gss_buffer_t token,
151                             char **out,
152                             unsigned *outlen);
153
154 static int gs2_get_mech_attrs(const sasl_utils_t *utils,
155                               const gss_OID mech,
156                               unsigned int *security_flags,
157                               unsigned int *features,
158                               const unsigned long **prompts);
159
160 static int gs2_indicate_mechs(const sasl_utils_t *utils);
161
162 static int gs2_map_sasl_name(const sasl_utils_t *utils,
163                              const char *mech,
164                              gss_OID *oid);
165
166 static int gs2_duplicate_buffer(const sasl_utils_t *utils,
167                                 const gss_buffer_t src,
168                                 gss_buffer_t dst);
169
170 static int gs2_unescape_authzid(const sasl_utils_t *utils,
171                                 char **in,
172                                 unsigned *inlen,
173                                 char **authzid);
174
175 static int gs2_escape_authzid(const sasl_utils_t *utils,
176                               const char *in,
177                               unsigned inlen,
178                               char **authzid);
179
180 /* sasl_gs_log: only logs status string returned from gss_display_status() */
181 #define sasl_gs2_log(x,y,z) sasl_gs2_seterror_(x,y,z,1)
182 #define sasl_gs2_seterror(x,y,z) sasl_gs2_seterror_(x,y,z,0)
183
184 static int
185 sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
186                    int logonly);
187
188 static context_t *
189 sasl_gs2_new_context(const sasl_utils_t *utils)
190 {
191     context_t *ret;
192
193     ret = utils->malloc(sizeof(context_t));
194     if (ret == NULL)
195         return NULL;
196
197     memset(ret, 0, sizeof(context_t));
198     ret->utils = utils;
199
200     return ret;
201 }
202
203 static int
204 sasl_gs2_free_context_contents(context_t *text)
205 {
206     OM_uint32 min_stat;
207
208     if (text == NULL)
209         return SASL_OK;
210
211     if (text->gss_ctx != GSS_C_NO_CONTEXT) {
212         gss_delete_sec_context(&min_stat,&text->gss_ctx,
213                                GSS_C_NO_BUFFER);
214         text->gss_ctx = GSS_C_NO_CONTEXT;
215     }
216
217     if (text->client_name != GSS_C_NO_NAME) {
218         gss_release_name(&min_stat,&text->client_name);
219         text->client_name = GSS_C_NO_NAME;
220     }
221
222     if (text->server_name != GSS_C_NO_NAME) {
223         gss_release_name(&min_stat,&text->server_name);
224         text->server_name = GSS_C_NO_NAME;
225     }
226
227     if (text->server_creds != GSS_C_NO_CREDENTIAL) {
228         gss_release_cred(&min_stat, &text->server_creds);
229         text->server_creds = GSS_C_NO_CREDENTIAL;
230     }
231
232     if (text->client_creds != GSS_C_NO_CREDENTIAL) {
233         gss_release_cred(&min_stat, &text->client_creds);
234         text->client_creds = GSS_C_NO_CREDENTIAL;
235     }
236
237     if (text->authid != NULL) {
238         text->utils->free(text->authid);
239         text->authid = NULL;
240     }
241
242     if (text->authzid != NULL) {
243         text->utils->free(text->authzid);
244         text->authzid = NULL;
245     }
246
247     gss_release_buffer(&min_stat, &text->gss_cbindings.application_data);
248
249     if (text->out_buf != NULL) {
250         text->utils->free(text->out_buf);
251         text->out_buf = NULL;
252     }
253
254     text->out_buf_len = 0;
255
256     if (text->cbindingname != NULL) {
257         text->utils->free(text->cbindingname);
258         text->cbindingname = NULL;
259     }
260
261     if (text->free_password)
262         _plug_free_secret(text->utils, &text->password);
263
264     memset(text, 0, sizeof(*text));
265
266     return SASL_OK;
267 }
268
269 static void
270 gs2_common_mech_dispose(void *conn_context, const sasl_utils_t *utils)
271 {
272     sasl_gs2_free_context_contents((context_t *)(conn_context));
273     utils->free(conn_context);
274 }
275
276 static void
277 gs2_common_mech_free(void *global_context __attribute__((unused)),
278                      const sasl_utils_t *utils)
279 {
280     OM_uint32 minor;
281
282     if (gs2_mechs != GSS_C_NO_OID_SET) {
283         gss_release_oid_set(&minor, &gs2_mechs);
284         gs2_mechs = GSS_C_NO_OID_SET;
285     }
286 }
287
288 /*****************************  Server Section  *****************************/
289
290 static int
291 gs2_server_mech_new(void *glob_context,
292                     sasl_server_params_t *params,
293                     const char *challenge __attribute__((unused)),
294                     unsigned challen __attribute__((unused)),
295                     void **conn_context)
296 {
297     context_t *text;
298     int ret;
299
300     text = sasl_gs2_new_context(params->utils);
301     if (text == NULL) {
302         MEMERROR(params->utils);
303         return SASL_NOMEM;
304     }
305
306     text->gss_ctx = GSS_C_NO_CONTEXT;
307     text->client_name = GSS_C_NO_NAME;
308     text->server_name = GSS_C_NO_NAME;
309     text->server_creds = GSS_C_NO_CREDENTIAL;
310     text->client_creds = GSS_C_NO_CREDENTIAL;
311     text->plug.server = glob_context;
312
313     ret = gs2_map_sasl_name(params->utils, text->plug.server->mech_name,
314                             &text->mechanism);
315     if (ret != SASL_OK) {
316         gs2_common_mech_dispose(text, params->utils);
317         return ret;
318     }
319
320     *conn_context = text;
321
322     return SASL_OK;
323 }
324
325 static int
326 gs2_server_mech_step(void *conn_context,
327                      sasl_server_params_t *params,
328                      const char *clientin,
329                      unsigned clientinlen,
330                      const char **serverout,
331                      unsigned *serveroutlen,
332                      sasl_out_params_t *oparams)
333 {
334     context_t *text = (context_t *)conn_context;
335     gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
336     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
337     OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
338     gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
339     gss_buffer_desc short_name_buf = GSS_C_EMPTY_BUFFER;
340     gss_name_t without = GSS_C_NO_NAME;
341     gss_OID_set_desc mechs;
342     OM_uint32 out_flags = 0;
343     int ret = 0, equal = 0;
344     int initialContextToken = (text->gss_ctx == GSS_C_NO_CONTEXT);
345     char *p;
346
347     if (serverout == NULL) {
348         PARAMERROR(text->utils);
349         return SASL_BADPARAM;
350     }
351
352     *serverout = NULL;
353     *serveroutlen = 0;
354
355     if (initialContextToken) {
356         name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
357         name_buf.value = params->utils->malloc(name_buf.length + 1);
358         if (name_buf.value == NULL) {
359             MEMERROR(text->utils);
360             ret = SASL_NOMEM;
361             goto cleanup;
362         }
363         snprintf(name_buf.value, name_buf.length + 1,
364                  "%s@%s", params->service, params->serverFQDN);
365         maj_stat = gss_import_name(&min_stat,
366                                    &name_buf,
367                                    GSS_C_NT_HOSTBASED_SERVICE,
368                                    &text->server_name);
369         params->utils->free(name_buf.value);
370         name_buf.value = NULL;
371
372         if (GSS_ERROR(maj_stat))
373             goto cleanup;
374
375         assert(text->server_creds == GSS_C_NO_CREDENTIAL);
376
377         mechs.count = 1;
378         mechs.elements = (gss_OID)text->mechanism;
379
380         if (params->gss_creds == GSS_C_NO_CREDENTIAL) {
381             maj_stat = gss_acquire_cred(&min_stat,
382                                         text->server_name,
383                                         GSS_C_INDEFINITE,
384                                         &mechs,
385                                         GSS_C_ACCEPT,
386                                         &text->server_creds,
387                                         NULL,
388                                         &text->lifetime);
389             if (GSS_ERROR(maj_stat))
390                 goto cleanup;
391         }
392
393         ret = gs2_verify_initial_message(text,
394                                          params,
395                                          clientin,
396                                          clientinlen,
397                                          &input_token);
398         if (ret != SASL_OK)
399             goto cleanup;
400     } else {
401         input_token.value = (void *)clientin;
402         input_token.length = clientinlen;
403     }
404
405     maj_stat = gss_accept_sec_context(&min_stat,
406                                       &text->gss_ctx,
407                                       (params->gss_creds != GSS_C_NO_CREDENTIAL)
408                                         ? (gss_cred_id_t)params->gss_creds
409                                         : text->server_creds,
410                                       &input_token,
411                                       &text->gss_cbindings,
412                                       &text->client_name,
413                                       NULL,
414                                       &output_token,
415                                       &out_flags,
416                                       &text->lifetime,
417                                       &text->client_creds);
418     if (GSS_ERROR(maj_stat)) {
419         sasl_gs2_log(text->utils, maj_stat, min_stat);
420         text->utils->seterror(text->utils->conn, SASL_NOLOG,
421                               "GS2 Failure: gss_accept_sec_context");
422         ret = (maj_stat == GSS_S_BAD_BINDINGS) ? SASL_BADBINDING : SASL_BADAUTH;
423         goto cleanup;
424     }
425
426     *serveroutlen = output_token.length;
427     if (output_token.value != NULL) {
428         ret = _plug_buf_alloc(text->utils, &text->out_buf,
429                               &text->out_buf_len, *serveroutlen);
430         if (ret != SASL_OK)
431             goto cleanup;
432         memcpy(text->out_buf, output_token.value, *serveroutlen);
433         *serverout = text->out_buf;
434     } else {
435         /* No output token, send an empty string */
436         *serverout = "";
437         serveroutlen = 0;
438     }
439
440     if (maj_stat == GSS_S_CONTINUE_NEEDED) {
441         ret = SASL_CONTINUE;
442         goto cleanup;
443     }
444
445     assert(maj_stat == GSS_S_COMPLETE);
446
447     maj_stat = gss_display_name(&min_stat, text->client_name,
448                                 &name_buf, NULL);
449     if (GSS_ERROR(maj_stat))
450         goto cleanup;
451
452     ret = gs2_duplicate_buffer(params->utils, &name_buf, &short_name_buf);
453     if (ret != 0)
454         goto cleanup;
455
456     p = (char *)memchr(name_buf.value, '@', name_buf.length);
457     if (p != NULL) {
458         short_name_buf.length = (p - (char *)name_buf.value);
459
460         maj_stat = gss_import_name(&min_stat,
461                                    &short_name_buf,
462                                    GSS_C_NT_USER_NAME,
463                                    &without);
464         if (GSS_ERROR(maj_stat)) {
465             ret = SASL_FAIL;
466             goto cleanup;
467         }
468
469         maj_stat = gss_compare_name(&min_stat, text->client_name,
470                                     without, &equal);
471         if (GSS_ERROR(maj_stat)) {
472             ret = SASL_FAIL;
473             goto cleanup;
474         }
475
476         if (equal)
477             ((char *)short_name_buf.value)[short_name_buf.length] = '\0';
478     }
479
480     text->authid = (char *)short_name_buf.value;
481     short_name_buf.value = NULL;
482     short_name_buf.length = 0;
483
484     if (text->authzid != NULL) {
485         ret = params->canon_user(params->utils->conn,
486                                  text->authzid, 0,
487                                  SASL_CU_AUTHZID, oparams);
488         if (ret != SASL_OK)
489             goto cleanup;
490     }
491
492     ret = params->canon_user(params->utils->conn,
493                              text->authid, 0,
494                              text->authzid == NULL
495                                 ? (SASL_CU_AUTHZID | SASL_CU_AUTHID)
496                                 : SASL_CU_AUTHID,
497                              oparams);
498     if (ret != SASL_OK)
499         goto cleanup;
500
501     switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
502     case GS2_CB_FLAG_N:
503         oparams->cbindingdisp = SASL_CB_DISP_NONE;
504         break;
505     case GS2_CB_FLAG_P:
506         oparams->cbindingdisp = SASL_CB_DISP_USED;
507         oparams->cbindingname = text->cbindingname;
508         break;
509     case GS2_CB_FLAG_Y:
510         oparams->cbindingdisp = SASL_CB_DISP_WANT;
511         break;
512     }
513
514     if (text->client_creds != GSS_C_NO_CREDENTIAL)
515         oparams->client_creds = &text->client_creds;
516     else
517         oparams->client_creds = NULL;
518
519     oparams->gss_peer_name = text->client_name;
520     oparams->gss_local_name = text->server_name;
521     oparams->maxoutbuf = 0xFFFFFF;
522     oparams->encode = NULL;
523     oparams->decode = NULL;
524     oparams->mech_ssf = 0;
525     oparams->doneflag = 1;
526
527     ret = SASL_OK;
528
529 cleanup:
530     if (initialContextToken)
531         gss_release_buffer(&min_stat, &input_token);
532     gss_release_buffer(&min_stat, &name_buf);
533     gss_release_buffer(&min_stat, &short_name_buf);
534     gss_release_buffer(&min_stat, &output_token);
535     gss_release_name(&min_stat, &without);
536
537     if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
538         sasl_gs2_seterror(text->utils, maj_stat, min_stat);
539         ret = SASL_FAIL;
540     }
541     if (ret < SASL_OK)
542         sasl_gs2_free_context_contents(text);
543
544     return ret;
545 }
546
547 static int
548 gs2_common_plug_init(const sasl_utils_t *utils,
549                      size_t plugsize,
550                      int (*plug_alloc)(const sasl_utils_t *,
551                                        void *,
552                                        const gss_buffer_t,
553                                        const gss_OID),
554                      void **pluglist,
555                      int *plugcount)
556 {
557     OM_uint32 major, minor;
558     size_t i, count = 0;
559     void *plugs = NULL;
560
561     *pluglist = NULL;
562     *plugcount = 0;
563
564     if (gs2_indicate_mechs(utils) != SASL_OK) {
565         return SASL_NOMECH;
566     }
567
568     plugs = utils->malloc(gs2_mechs->count * plugsize);
569     if (plugs == NULL) {
570         MEMERROR(utils);
571         return SASL_NOMEM;
572     }
573     memset(plugs, 0, gs2_mechs->count * plugsize);
574
575     for (i = 0; i < gs2_mechs->count; i++) {
576         gss_buffer_desc sasl_mech_name = GSS_C_EMPTY_BUFFER;
577
578         major = gss_inquire_saslname_for_mech(&minor,
579                                               &gs2_mechs->elements[i],
580                                               &sasl_mech_name,
581                                               GSS_C_NO_BUFFER,
582                                               GSS_C_NO_BUFFER);
583         if (GSS_ERROR(major))
584             continue;
585
586 #define PLUG_AT(index)      (void *)((unsigned char *)plugs + (count * plugsize))
587
588         if (plug_alloc(utils, PLUG_AT(count), &sasl_mech_name,
589                        &gs2_mechs->elements[i]) == SASL_OK)
590             count++;
591
592         gss_release_buffer(&minor, &sasl_mech_name);
593     }
594
595     if (count == 0) {
596         utils->free(plugs);
597         return SASL_NOMECH;
598     }
599
600     *pluglist = plugs;
601     *plugcount = count;
602
603     return SASL_OK;
604 }
605
606 static int
607 gs2_server_plug_alloc(const sasl_utils_t *utils,
608                       void *plug,
609                       gss_buffer_t sasl_name,
610                       gss_OID mech)
611 {
612     int ret;
613     sasl_server_plug_t *splug = (sasl_server_plug_t *)plug;
614     gss_buffer_desc buf;
615
616     memset(splug, 0, sizeof(*splug));
617
618     ret = gs2_get_mech_attrs(utils, mech,
619                              &splug->security_flags,
620                              &splug->features,
621                              NULL);
622     if (ret != SASL_OK)
623         return ret;
624
625     ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
626     if (ret != SASL_OK)
627         return ret;
628
629     splug->mech_name = (char *)buf.value;
630     splug->glob_context = plug;
631     splug->mech_new = gs2_server_mech_new;
632     splug->mech_step = gs2_server_mech_step;
633     splug->mech_dispose = gs2_common_mech_dispose;
634     splug->mech_free = gs2_common_mech_free;
635
636     return SASL_OK;
637 }
638
639 static sasl_server_plug_t *gs2_server_plugins;
640 static int gs2_server_plugcount;
641
642 int
643 gs2_server_plug_init(const sasl_utils_t *utils,
644                      int maxversion,
645                      int *outversion,
646                      sasl_server_plug_t **pluglist,
647                      int *plugcount)
648 {
649     int ret;
650
651     *pluglist = NULL;
652     *plugcount = 0;
653
654     if (maxversion < SASL_SERVER_PLUG_VERSION)
655         return SASL_BADVERS;
656
657     *outversion = SASL_SERVER_PLUG_VERSION;
658
659     if (gs2_server_plugins == NULL) {
660         ret = gs2_common_plug_init(utils,
661                                    sizeof(sasl_server_plug_t),
662                                    gs2_server_plug_alloc,
663                                    (void **)&gs2_server_plugins,
664                                    &gs2_server_plugcount);
665         if (ret != SASL_OK)
666             return ret;
667     }
668
669     *pluglist = gs2_server_plugins;
670     *plugcount = gs2_server_plugcount;
671
672     return SASL_OK;
673 }
674
675 /*****************************  Client Section  *****************************/
676
677 static int gs2_client_mech_step(void *conn_context,
678                                 sasl_client_params_t *params,
679                                 const char *serverin,
680                                 unsigned serverinlen,
681                                 sasl_interact_t **prompt_need,
682                                 const char **clientout,
683                                 unsigned *clientoutlen,
684                                 sasl_out_params_t *oparams)
685 {
686     context_t *text = (context_t *)conn_context;
687     gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
688     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
689     gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
690     OM_uint32 maj_stat = GSS_S_FAILURE, min_stat = 0;
691     OM_uint32 ret_flags;
692     int ret = SASL_FAIL;
693     int initialContextToken;
694
695     *clientout = NULL;
696     *clientoutlen = 0;
697
698     if (text->gss_ctx == GSS_C_NO_CONTEXT) {
699         ret = gs2_get_init_creds(text, params, prompt_need, oparams);
700         if (ret != SASL_OK)
701             goto cleanup;
702
703         initialContextToken = 1;
704     } else
705         initialContextToken = 0;
706
707     if (text->server_name == GSS_C_NO_NAME) { /* only once */
708         name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
709         name_buf.value = params->utils->malloc(name_buf.length + 1);
710         if (name_buf.value == NULL) {
711             ret = SASL_NOMEM;
712             goto cleanup;
713         }
714         if (params->serverFQDN == NULL ||
715             strlen(params->serverFQDN) == 0) {
716             SETERROR(text->utils, "GS2 Failure: no serverFQDN");
717             ret = SASL_FAIL;
718             goto cleanup;
719         }
720
721         snprintf(name_buf.value, name_buf.length + 1,
722                  "%s@%s", params->service, params->serverFQDN);
723
724         maj_stat = gss_import_name(&min_stat,
725                                    &name_buf,
726                                    GSS_C_NT_HOSTBASED_SERVICE,
727                                    &text->server_name);
728         params->utils->free(name_buf.value);
729         name_buf.value = NULL;
730
731         if (GSS_ERROR(maj_stat))
732             goto cleanup;
733     }
734
735     /* From GSSAPI plugin: apparently this is for some IMAP bug workaround */
736     if (serverinlen == 0 && text->gss_ctx != GSS_C_NO_CONTEXT) {
737         gss_delete_sec_context(&min_stat, &text->gss_ctx, GSS_C_NO_BUFFER);
738         text->gss_ctx = GSS_C_NO_CONTEXT;
739     }
740
741     input_token.value = (void *)serverin;
742     input_token.length = serverinlen;
743
744     if (initialContextToken) {
745         if ((text->plug.client->features & SASL_FEAT_GSS_FRAMING) == 0)
746             text->gs2_flags |= GS2_NONSTD_FLAG;
747
748         switch (params->cbindingdisp) {
749         case SASL_CB_DISP_NONE:
750             text->gs2_flags |= GS2_CB_FLAG_N;
751             break;
752         case SASL_CB_DISP_USED:
753             text->gs2_flags |= GS2_CB_FLAG_P;
754             break;
755         case SASL_CB_DISP_WANT:
756             text->gs2_flags |= GS2_CB_FLAG_Y;
757             break;
758         }
759
760         ret = gs2_make_header(text, params,
761                               strcmp(oparams->user, oparams->authid) ?
762                                      (char *) oparams->user : NULL,
763                               &text->out_buf, &text->out_buf_len);
764         if (ret != 0)
765             goto cleanup;
766     }
767
768     maj_stat = gss_init_sec_context(&min_stat,
769                                     (params->gss_creds != GSS_C_NO_CREDENTIAL)
770                                         ? (gss_cred_id_t)params->gss_creds
771                                         : text->client_creds,
772                                     &text->gss_ctx,
773                                     text->server_name,
774                                     (gss_OID)text->mechanism,
775                                     GSS_C_MUTUAL_FLAG,
776                                     GSS_C_INDEFINITE,
777                                     &text->gss_cbindings,
778                                     serverinlen ? &input_token : GSS_C_NO_BUFFER,
779                                     NULL,
780                                     &output_token,
781                                     &ret_flags,
782                                     &text->lifetime);
783     if (GSS_ERROR(maj_stat))
784         goto cleanup;
785
786     ret = gs2_make_message(text, params, initialContextToken, &output_token,
787                            &text->out_buf, &text->out_buf_len);
788     if (ret != 0)
789         goto cleanup;
790
791     *clientout = text->out_buf;
792     *clientoutlen = text->out_buf_len;
793
794     if (maj_stat == GSS_S_CONTINUE_NEEDED) {
795         ret = SASL_CONTINUE;
796         goto cleanup;
797     }
798
799     if (text->client_name != GSS_C_NO_NAME)
800         gss_release_name(&min_stat, &text->client_name);
801
802     maj_stat = gss_inquire_context(&min_stat,
803                                    text->gss_ctx,
804                                    &text->client_name,
805                                    NULL,
806                                    &text->lifetime,
807                                    NULL,
808                                    &ret_flags, /* flags */
809                                    NULL,
810                                    NULL);
811     if (GSS_ERROR(maj_stat))
812         goto cleanup;
813
814     if (params->cbindingdisp != SASL_CB_DISP_NONE &&
815         (ret_flags & GSS_C_MUTUAL_FLAG) == 0) {
816         maj_stat = SASL_BADAUTH;
817         goto cleanup;
818     }
819
820     maj_stat = gss_display_name(&min_stat,
821                                 text->client_name,
822                                 &name_buf,
823                                 NULL);
824     if (GSS_ERROR(maj_stat))
825         goto cleanup;
826
827     oparams->gss_peer_name = text->server_name;
828     oparams->gss_local_name = text->client_name;
829     oparams->encode = NULL;
830     oparams->decode = NULL;
831     oparams->mech_ssf = 0;
832     oparams->maxoutbuf = 0xFFFFFF;
833     oparams->doneflag = 1;
834
835 cleanup:
836     gss_release_buffer(&min_stat, &output_token);
837     gss_release_buffer(&min_stat, &name_buf);
838
839     if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
840         sasl_gs2_seterror(text->utils, maj_stat, min_stat);
841         ret = (maj_stat & GSS_S_PROMPTING_NEEDED) ? SASL_INTERACT : SASL_FAIL;
842     }
843     if (ret < SASL_OK)
844         sasl_gs2_free_context_contents(text);
845
846     return ret;
847 }
848
849 static int gs2_client_mech_new(void *glob_context,
850                                sasl_client_params_t *params,
851                                void **conn_context)
852 {
853     context_t *text;
854     int ret;
855
856     text = sasl_gs2_new_context(params->utils);
857     if (text == NULL) {
858         MEMERROR(params->utils);
859         return SASL_NOMEM;
860     }
861
862     text->gss_ctx = GSS_C_NO_CONTEXT;
863     text->client_name = GSS_C_NO_NAME;
864     text->server_creds = GSS_C_NO_CREDENTIAL;
865     text->client_creds  = GSS_C_NO_CREDENTIAL;
866     text->plug.client = glob_context;
867
868     ret = gs2_map_sasl_name(params->utils, text->plug.client->mech_name,
869                             &text->mechanism);
870     if (ret != SASL_OK) {
871         gs2_common_mech_dispose(text, params->utils);
872         return ret;
873     }
874
875     *conn_context = text;
876
877     return SASL_OK;
878 }
879
880 static int
881 gs2_client_plug_alloc(const sasl_utils_t *utils,
882                       void *plug,
883                       gss_buffer_t sasl_name,
884                       gss_OID mech)
885 {
886     int ret;
887     sasl_client_plug_t *cplug = (sasl_client_plug_t *)plug;
888     gss_buffer_desc buf;
889
890     memset(cplug, 0, sizeof(*cplug));
891
892     ret = gs2_get_mech_attrs(utils, mech,
893                              &cplug->security_flags,
894                              &cplug->features,
895                              &cplug->required_prompts);
896     if (ret != SASL_OK)
897         return ret;
898
899     ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
900     if (ret != SASL_OK)
901         return ret;
902
903     cplug->mech_name = (char *)buf.value;
904     cplug->features |= SASL_FEAT_NEEDSERVERFQDN;
905     cplug->glob_context = plug;
906     cplug->mech_new = gs2_client_mech_new;
907     cplug->mech_step = gs2_client_mech_step;
908     cplug->mech_dispose = gs2_common_mech_dispose;
909     cplug->mech_free = gs2_common_mech_free;
910
911     return SASL_OK;
912 }
913
914 static sasl_client_plug_t *gs2_client_plugins;
915 static int gs2_client_plugcount;
916
917 int
918 gs2_client_plug_init(const sasl_utils_t *utils,
919                      int maxversion,
920                      int *outversion,
921                      sasl_client_plug_t **pluglist,
922                      int *plugcount)
923 {
924     int ret;
925
926     *pluglist = NULL;
927     *plugcount = 0;
928
929     if (maxversion < SASL_CLIENT_PLUG_VERSION)
930         return SASL_BADVERS;
931
932     *outversion = SASL_CLIENT_PLUG_VERSION;
933
934     if (gs2_client_plugins == NULL) {
935         ret = gs2_common_plug_init(utils,
936                                    sizeof(sasl_client_plug_t),
937                                    gs2_client_plug_alloc,
938                                    (void **)&gs2_client_plugins,
939                                    &gs2_client_plugcount);
940         if (ret != SASL_OK)
941             return ret;
942     }
943
944     *pluglist = gs2_client_plugins;
945     *plugcount = gs2_client_plugcount;
946
947     return SASL_OK;
948 }
949
950 /*
951  * Copy header and application channel bindings to GSS channel bindings
952  * structure in context.
953  */
954 static int
955 gs2_save_cbindings(context_t *text,
956                    gss_buffer_t header,
957                    const sasl_channel_binding_t *cbinding)
958 {
959     gss_buffer_t gss_cbindings = &text->gss_cbindings.application_data;
960     size_t len;
961     unsigned char *p;
962
963     assert(gss_cbindings->value == NULL);
964
965     /*
966      * The application-data field MUST be set to the gs2-header, excluding
967      * the initial [gs2-nonstd-flag ","] part, concatenated with, when a
968      * gs2-cb-flag of "p" is used, the application's channel binding data.
969      */
970     len = header->length;
971     if (text->gs2_flags & GS2_NONSTD_FLAG) {
972         assert(len > 2);
973         len -= 2;
974     }
975     if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
976         cbinding != NULL) {
977         len += cbinding->len;
978     }
979
980     gss_cbindings->length = len;
981     gss_cbindings->value = text->utils->malloc(len);
982     if (gss_cbindings->value == NULL)
983         return SASL_NOMEM;
984
985     p = (unsigned char *)gss_cbindings->value;
986     if (text->gs2_flags & GS2_NONSTD_FLAG) {
987         memcpy(p, (unsigned char *)header->value + 2, header->length - 2);
988         p += header->length - 2;
989     } else {
990         memcpy(p, header->value, header->length);
991         p += header->length;
992     }
993
994     if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
995         cbinding != NULL) {
996         memcpy(p, cbinding->data, cbinding->len);
997     }
998
999     return SASL_OK;
1000 }
1001
1002 #define CHECK_REMAIN(n)     do { if (remain < (n)) return SASL_BADPROT; } while (0)
1003
1004 /*
1005  * Verify gs2-header, save authzid and channel bindings to context.
1006  */
1007 static int
1008 gs2_verify_initial_message(context_t *text,
1009                            sasl_server_params_t *sparams,
1010                            const char *in,
1011                            unsigned inlen,
1012                            gss_buffer_t token)
1013 {
1014     OM_uint32 major, minor;
1015     char *p = (char *)in;
1016     unsigned remain = inlen;
1017     int ret;
1018     gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
1019
1020     assert(text->cbindingname == NULL);
1021     assert(text->authzid == NULL);
1022
1023     token->length = 0;
1024     token->value = NULL;
1025
1026     /* minimum header includes CB flag and non-zero GSS token */
1027     CHECK_REMAIN(4); /* [pny],,. */
1028
1029     /* non-standard GSS framing flag */
1030     if (remain > 1 && memcmp(p, "F,", 2) == 0) {
1031         text->gs2_flags |= GS2_NONSTD_FLAG;
1032         remain -= 2;
1033         p += 2;
1034     }
1035
1036     /* SASL channel bindings */
1037     CHECK_REMAIN(1); /* [pny] */
1038     remain--;
1039     switch (*p++) {
1040     case 'p':
1041         CHECK_REMAIN(1); /* = */
1042         remain--;
1043         if (*p++ != '=')
1044             return SASL_BADPROT;
1045
1046         ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->cbindingname);
1047         if (ret != SASL_OK)
1048             return ret;
1049
1050         text->gs2_flags |= GS2_CB_FLAG_P;
1051         break;
1052     case 'n':
1053         text->gs2_flags |= GS2_CB_FLAG_N;
1054         break;
1055     case 'y':
1056         text->gs2_flags |= GS2_CB_FLAG_Y;
1057         break;
1058     }
1059
1060     CHECK_REMAIN(1); /* , */
1061     remain--;
1062     if (*p++ != ',')
1063         return SASL_BADPROT;
1064
1065     /* authorization identity */
1066     if (remain > 1 && memcmp(p, "a=", 2) == 0) {
1067         CHECK_REMAIN(2);
1068         remain -= 2;
1069         p += 2;
1070
1071         ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->authzid);
1072         if (ret != SASL_OK)
1073             return ret;
1074     }
1075
1076     /* end of header */
1077     CHECK_REMAIN(1); /* , */
1078     remain--;
1079     if (*p++ != ',')
1080         return SASL_BADPROT;
1081
1082     buf.length = inlen - remain;
1083     buf.value = (void *)in;
1084
1085     /* stash channel bindings to pass into gss_accept_sec_context() */
1086     ret = gs2_save_cbindings(text, &buf, sparams->cbinding);
1087     if (ret != SASL_OK)
1088         return ret;
1089
1090     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1091         buf.length = remain;
1092         buf.value = p;
1093     } else {
1094         gss_buffer_desc tmp;
1095
1096         tmp.length = remain;
1097         tmp.value = p;
1098
1099         major = gss_encapsulate_token(&tmp, text->mechanism, &buf);
1100         if (GSS_ERROR(major))
1101             return SASL_NOMEM;
1102     }
1103
1104     token->value = text->utils->malloc(buf.length);
1105     if (token->value == NULL)
1106         return SASL_NOMEM;
1107
1108     token->length = buf.length;
1109     memcpy(token->value, buf.value, buf.length);
1110
1111     if ((text->gs2_flags & GS2_NONSTD_FLAG) == 0)
1112         gss_release_buffer(&minor, &buf);
1113
1114     return SASL_OK;
1115 }
1116
1117 /*
1118  * Create gs2-header, save channel bindings to context.
1119  */
1120 static int
1121 gs2_make_header(context_t *text,
1122                 sasl_client_params_t *cparams,
1123                 const char *authzid,
1124                 char **out,
1125                 unsigned *outlen)
1126 {
1127     size_t required = 0;
1128     size_t wire_authzid_len = 0, cbnamelen = 0;
1129     char *wire_authzid = NULL;
1130     char *p;
1131     int ret;
1132     gss_buffer_desc buf;
1133
1134     *out = NULL;
1135     *outlen = 0;
1136
1137     /* non-standard GSS framing flag */
1138     if (text->gs2_flags & GS2_NONSTD_FLAG)
1139         required += 2; /* F, */
1140
1141     /* SASL channel bindings */
1142     switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
1143     case GS2_CB_FLAG_P:
1144         if (!SASL_CB_PRESENT(cparams))
1145             return SASL_BADPARAM;
1146         cbnamelen = strlen(cparams->cbinding->name);
1147         required += 1 /*=*/ + cbnamelen;
1148         /* fallthrough */
1149     case GS2_CB_FLAG_N:
1150     case GS2_CB_FLAG_Y:
1151         required += 2; /* [pny], */
1152         break;
1153     default:
1154         return SASL_BADPARAM;
1155     }
1156
1157     /* authorization identity */
1158     if (authzid != NULL) {
1159         ret = gs2_escape_authzid(text->utils, authzid,
1160                                  strlen(authzid), &wire_authzid);
1161         if (ret != SASL_OK)
1162             return ret;
1163
1164         wire_authzid_len = strlen(wire_authzid);
1165         required += 2 /* a= */ + wire_authzid_len;
1166     }
1167
1168     required += 1; /* trailing comma */
1169
1170     ret = _plug_buf_alloc(text->utils, out, outlen, required);
1171     if (ret != SASL_OK) {
1172         text->utils->free(wire_authzid);
1173         return ret;
1174     }
1175
1176     *out = text->out_buf;
1177     *outlen = required;
1178
1179     p = (char *)text->out_buf;
1180     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1181         *p++ = 'F';
1182         *p++ = ',';
1183     }
1184     switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
1185     case GS2_CB_FLAG_P:
1186         memcpy(p, "p=", 2);
1187         memcpy(p + 2, cparams->cbinding->name, cbnamelen);
1188         p += 2 + cbnamelen;
1189         break;
1190     case GS2_CB_FLAG_N:
1191         *p++ = 'n';
1192         break;
1193     case GS2_CB_FLAG_Y:
1194         *p++ = 'y';
1195         break;
1196     }
1197     *p++ = ',';
1198     if (wire_authzid != NULL) {
1199         memcpy(p, "a=", 2);
1200         memcpy(p + 2, wire_authzid, wire_authzid_len);
1201         text->utils->free(wire_authzid);
1202         p += 2 + wire_authzid_len;
1203     }
1204     *p++ = ',';
1205
1206     assert(p == (char *)text->out_buf + required);
1207
1208     buf.length = required;
1209     buf.value = *out;
1210
1211     ret = gs2_save_cbindings(text, &buf, cparams->cbinding);
1212     if (ret != SASL_OK)
1213         return ret;
1214
1215     return SASL_OK;
1216 }
1217
1218 /*
1219  * Convert a GSS token to a GS2 one
1220  */
1221 static int
1222 gs2_make_message(context_t *text,
1223                  sasl_client_params_t *cparams __attribute__((unused)),
1224                  int initialContextToken,
1225                  gss_buffer_t token,
1226                  char **out,
1227                  unsigned *outlen)
1228 {
1229     OM_uint32 major, minor;
1230     int ret;
1231     unsigned header_len = 0;
1232     gss_buffer_desc decap_token = GSS_C_EMPTY_BUFFER;
1233
1234     if (initialContextToken) {
1235         header_len = *outlen;
1236
1237         major = gss_decapsulate_token(token, text->mechanism, &decap_token);
1238         if ((major == GSS_S_DEFECTIVE_TOKEN &&
1239              (text->plug.client->features & SASL_FEAT_GSS_FRAMING)) ||
1240             GSS_ERROR(major))
1241             return SASL_FAIL;
1242
1243         token = &decap_token;
1244     }
1245
1246     ret = _plug_buf_alloc(text->utils, out, outlen,
1247                           header_len + token->length);
1248     if (ret != 0)
1249         return ret;
1250
1251     memcpy(*out + header_len, token->value, token->length);
1252     *outlen = header_len + token->length;
1253
1254     if (initialContextToken)
1255         gss_release_buffer(&minor, &decap_token);
1256
1257     return SASL_OK;
1258 }
1259
1260 static const unsigned long gs2_required_prompts[] = {
1261     SASL_CB_LIST_END
1262 };
1263
1264 /*
1265  * Map GSS mechanism attributes to SASL ones
1266  */
1267 static int
1268 gs2_get_mech_attrs(const sasl_utils_t *utils,
1269                    const gss_OID mech,
1270                    unsigned int *security_flags,
1271                    unsigned int *features,
1272                    const unsigned long **prompts)
1273 {
1274     OM_uint32 major, minor;
1275     int present;
1276     gss_OID_set attrs = GSS_C_NO_OID_SET;
1277
1278     major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
1279     if (GSS_ERROR(major)) {
1280         utils->seterror(utils->conn, SASL_NOLOG,
1281                         "GS2 Failure: gss_inquire_attrs_for_mech");
1282         return SASL_FAIL;
1283     }
1284
1285     *security_flags = SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE;
1286     *features = SASL_FEAT_WANT_CLIENT_FIRST;
1287     if (prompts != NULL)
1288         *prompts = gs2_required_prompts;
1289
1290 #define MA_PRESENT(a)   (gss_test_oid_set_member(&minor, (gss_OID)(a), \
1291                                                  attrs, &present) == GSS_S_COMPLETE && \
1292                          present)
1293
1294     if (MA_PRESENT(GSS_C_MA_PFS))
1295         *security_flags |= SASL_SEC_FORWARD_SECRECY;
1296     if (!MA_PRESENT(GSS_C_MA_AUTH_INIT_ANON))
1297         *security_flags |= SASL_SEC_NOANONYMOUS;
1298     if (MA_PRESENT(GSS_C_MA_DELEG_CRED))
1299         *security_flags |= SASL_SEC_PASS_CREDENTIALS;
1300     if (MA_PRESENT(GSS_C_MA_AUTH_TARG)) {
1301         *features |= SASL_FEAT_CHANNEL_BINDING;
1302         *security_flags |= SASL_SEC_MUTUAL_AUTH;
1303     }
1304     if (MA_PRESENT(GSS_C_MA_AUTH_INIT_INIT) && prompts != NULL)
1305         *prompts = NULL;
1306     if (MA_PRESENT(GSS_C_MA_ITOK_FRAMED))
1307         *features |= SASL_FEAT_GSS_FRAMING;
1308
1309     gss_release_oid_set(&minor, &attrs);
1310
1311     return SASL_OK;
1312 }
1313
1314 /*
1315  * Enumerate GSS mechanisms that can be used for GS2
1316  */
1317 static int gs2_indicate_mechs(const sasl_utils_t *utils)
1318 {
1319     OM_uint32 major, minor;
1320     gss_OID_desc desired_oids[2];
1321     gss_OID_set_desc desired_attrs;
1322     gss_OID_desc except_oids[3];
1323     gss_OID_set_desc except_attrs;
1324
1325     if (gs2_mechs != GSS_C_NO_OID_SET)
1326         return SASL_OK;
1327
1328     desired_oids[0] = *GSS_C_MA_AUTH_INIT;
1329     desired_oids[1] = *GSS_C_MA_CBINDINGS;
1330     desired_attrs.count = sizeof(desired_oids)/sizeof(desired_oids[0]);
1331     desired_attrs.elements = desired_oids;
1332
1333     except_oids[0] = *GSS_C_MA_MECH_NEGO;
1334     except_oids[1] = *GSS_C_MA_NOT_MECH;
1335     except_oids[2] = *GSS_C_MA_DEPRECATED;
1336
1337     except_attrs.count = sizeof(except_oids)/sizeof(except_oids[0]);
1338     except_attrs.elements = except_oids;
1339
1340     major = gss_indicate_mechs_by_attrs(&minor,
1341                                         &desired_attrs,
1342                                         &except_attrs,
1343                                         GSS_C_NO_OID_SET,
1344                                         &gs2_mechs);
1345     if (GSS_ERROR(major)) {
1346         utils->seterror(utils->conn, SASL_NOLOG,
1347                         "GS2 Failure: gss_indicate_mechs_by_attrs");
1348         return SASL_FAIL;
1349     }
1350
1351     return (gs2_mechs->count > 0) ? SASL_OK : SASL_NOMECH;
1352 }
1353
1354 /*
1355  * Map SASL mechanism name to OID
1356  */
1357 static int
1358 gs2_map_sasl_name(const sasl_utils_t *utils,
1359                   const char *mech,
1360                   gss_OID *oid)
1361 {
1362     OM_uint32 major, minor;
1363     gss_buffer_desc buf;
1364
1365     buf.length = strlen(mech);
1366     buf.value = (void *)mech;
1367
1368     major = gss_inquire_mech_for_saslname(&minor, &buf, oid);
1369     if (GSS_ERROR(major)) {
1370         utils->seterror(utils->conn, SASL_NOLOG,
1371                         "GS2 Failure: gss_inquire_mech_for_saslname");
1372         return SASL_FAIL;
1373     }
1374
1375     return SASL_OK;
1376 }
1377
1378 static int
1379 gs2_duplicate_buffer(const sasl_utils_t *utils,
1380                      const gss_buffer_t src,
1381                      gss_buffer_t dst)
1382 {
1383     dst->value = utils->malloc(src->length + 1);
1384     if (dst->value == NULL)
1385         return SASL_NOMEM;
1386
1387     memcpy(dst->value, src->value, src->length);
1388     ((char *)dst->value)[src->length] = '\0';
1389     dst->length = src->length;
1390
1391     return SASL_OK;
1392 }
1393
1394 static int
1395 gs2_unescape_authzid(const sasl_utils_t *utils,
1396                      char **endp,
1397                      unsigned *remain,
1398                      char **authzid)
1399 {
1400     char *in = *endp;
1401     size_t i, len, inlen = *remain;
1402     char *p;
1403
1404     *endp = NULL;
1405
1406     for (i = 0, len = 0; i < inlen; i++) {
1407         if (in[i] == ',') {
1408             *endp = &in[i];
1409             *remain -= i;
1410             break;
1411         } else if (in[i] == '=') {
1412             if (inlen <= i + 2)
1413                 return SASL_BADPROT;
1414             i += 2;
1415         }
1416         len++;
1417     }
1418
1419     if (len == 0 || *endp == NULL)
1420         return SASL_BADPROT;
1421
1422     p = *authzid = utils->malloc(len + 1);
1423     if (*authzid == NULL)
1424         return SASL_NOMEM;
1425
1426     for (i = 0; i < inlen; i++) {
1427         if (in[i] == ',')
1428             break;
1429         else if (in[i] == '=') {
1430             if (memcmp(&in[i + 1], "2C", 2) == 0)
1431                 *p++ = ',';
1432             else if (memcmp(&in[i + 1], "3D", 2) == 0)
1433                 *p++ = '=';
1434             else {
1435                 utils->free(*authzid);
1436                 *authzid = NULL;
1437                 return SASL_BADPROT;
1438             }
1439             i += 2;
1440         } else
1441             *p++ = in[i];
1442     }
1443
1444     *p = '\0';
1445
1446     return SASL_OK;
1447 }
1448
1449 static int
1450 gs2_escape_authzid(const sasl_utils_t *utils,
1451                    const char *in,
1452                    unsigned inlen,
1453                    char **authzid)
1454 {
1455     size_t i;
1456     char *p;
1457
1458     p = *authzid = utils->malloc((inlen * 3) + 1);
1459     if (*authzid == NULL)
1460         return SASL_NOMEM;
1461
1462     for (i = 0; i < inlen; i++) {
1463         if (in[i] == ',') {
1464             memcpy(p, "=2C", 3);
1465             p += 3;
1466         } else if (in[i] == '=') {
1467             memcpy(p, "=3D", 3);
1468             p += 3;
1469         } else {
1470             *p++ = in[i];
1471         }
1472     }
1473
1474     *p = '\0';
1475
1476     return SASL_OK;
1477 }
1478
1479 #define GOT_CREDS(text, params) ((text)->client_creds != NULL || (params)->gss_creds != NULL)
1480 #define CRED_ERROR(status)      ((status) == GSS_S_CRED_UNAVAIL || (status) == GSS_S_NO_CRED)
1481
1482 /*
1483  * Determine the authentication identity from the application supplied
1484  * GSS credential, the application supplied identity, and the default
1485  * GSS credential, in that order. Then, acquire credentials.
1486  */
1487 static int
1488 gs2_get_init_creds(context_t *text,
1489                    sasl_client_params_t *params,
1490                    sasl_interact_t **prompt_need,
1491                    sasl_out_params_t *oparams)
1492 {
1493     int result = SASL_OK;
1494     const char *authid = NULL, *userid = NULL;
1495     int user_result = SASL_OK;
1496     int auth_result = SASL_OK;
1497     int pass_result = SASL_OK;
1498     OM_uint32 maj_stat = GSS_S_COMPLETE, min_stat = 0;
1499     gss_OID_set_desc mechs;
1500     gss_buffer_desc cred_authid = GSS_C_EMPTY_BUFFER;
1501     gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER;
1502
1503     mechs.count = 1;
1504     mechs.elements = (gss_OID)text->mechanism;
1505
1506     /*
1507      * Get the authentication identity from the application.
1508      */
1509     if (oparams->authid == NULL) {
1510         auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
1511         if (auth_result != SASL_OK && auth_result != SASL_INTERACT) {
1512             result = auth_result;
1513             goto cleanup;
1514         }
1515     }
1516
1517     /*
1518      * Get the authorization identity from the application.
1519      */
1520     if (oparams->user == NULL) {
1521         user_result = _plug_get_userid(params->utils, &userid, prompt_need);
1522         if (user_result != SASL_OK && user_result != SASL_INTERACT) {
1523             result = user_result;
1524             goto cleanup;
1525         }
1526     }
1527
1528     /*
1529      * Canonicalize the authentication and authorization identities before
1530      * calling GSS_Import_name.
1531      */
1532     if (auth_result == SASL_OK && user_result == SASL_OK &&
1533         oparams->authid == NULL) {
1534         if (userid == NULL || userid[0] == '\0') {
1535             result = params->canon_user(params->utils->conn, authid, 0,
1536                                         SASL_CU_AUTHID | SASL_CU_AUTHZID,
1537                                         oparams);
1538         } else {
1539             result = params->canon_user(params->utils->conn,
1540                                         authid, 0, SASL_CU_AUTHID, oparams);
1541             if (result != SASL_OK)
1542                 goto cleanup;
1543
1544             result = params->canon_user(params->utils->conn,
1545                                         userid, 0, SASL_CU_AUTHZID, oparams);
1546             if (result != SASL_OK)
1547                 goto cleanup;
1548         }
1549
1550         if (oparams->authid != NULL) {
1551             name_buf.length = strlen(oparams->authid);
1552             name_buf.value = (void *)oparams->authid;
1553
1554             assert(text->client_name == GSS_C_NO_NAME);
1555
1556             maj_stat = gss_import_name(&min_stat,
1557                                        &name_buf,
1558                                        GSS_C_NT_USER_NAME,
1559                                        &text->client_name);
1560             if (GSS_ERROR(maj_stat))
1561                 goto cleanup;
1562
1563             /* The authid may have changed after prompting, so free any creds */
1564             gss_release_cred(&min_stat, &text->client_creds);
1565         }
1566     }
1567
1568     /*
1569      * If application didn't provide an authid, then use the default
1570      * credential. If that doesn't work, give up.
1571      */
1572     if (!GOT_CREDS(text, params) && oparams->authid == NULL) {
1573         maj_stat = gss_acquire_cred(&min_stat,
1574                                     GSS_C_NO_NAME,
1575                                     GSS_C_INDEFINITE,
1576                                     &mechs,
1577                                     GSS_C_INITIATE,
1578                                     &text->client_creds,
1579                                     NULL,
1580                                     &text->lifetime);
1581         if (GSS_ERROR(maj_stat))
1582             goto cleanup;
1583
1584         assert(text->client_name == GSS_C_NO_NAME);
1585
1586         maj_stat = gss_inquire_cred(&min_stat,
1587                                     params->gss_creds
1588                                         ? (gss_cred_id_t)params->gss_creds
1589                                         : text->client_creds,
1590                                     &text->client_name,
1591                                     NULL,
1592                                     NULL,
1593                                     NULL);
1594         if (GSS_ERROR(maj_stat)) {
1595             /* Maybe there was no default credential */
1596             auth_result = SASL_INTERACT;
1597             goto interact;
1598         }
1599
1600         maj_stat = gss_display_name(&min_stat,
1601                                     text->client_name,
1602                                     &cred_authid,
1603                                     NULL);
1604         if (GSS_ERROR(maj_stat))
1605             goto cleanup;
1606
1607         if (userid == NULL || userid[0] == '\0') {
1608             result = params->canon_user(params->utils->conn,
1609                                         cred_authid.value, cred_authid.length,
1610                                         SASL_CU_AUTHID | SASL_CU_AUTHZID,
1611                                         oparams);
1612         } else {
1613             result = params->canon_user(params->utils->conn,
1614                                         cred_authid.value, cred_authid.length,
1615                                         SASL_CU_AUTHID, oparams);
1616             if (result != SASL_OK)
1617                 goto cleanup;
1618
1619             result = params->canon_user(params->utils->conn,
1620                                         cred_authid.value, cred_authid.length,
1621                                         SASL_CU_AUTHZID, oparams);
1622             if (result != SASL_OK)
1623                 goto cleanup;
1624         }
1625     }
1626
1627     /*
1628      * Armed with the authentication identity, try to get a credential without
1629      * a password.
1630      */
1631     if (!GOT_CREDS(text, params) && text->client_name != GSS_C_NO_NAME) {
1632         maj_stat = gss_acquire_cred(&min_stat,
1633                                     text->client_name,
1634                                     GSS_C_INDEFINITE,
1635                                     &mechs,
1636                                     GSS_C_INITIATE,
1637                                     &text->client_creds,
1638                                     NULL,
1639                                     &text->lifetime);
1640         if (GSS_ERROR(maj_stat) && !CRED_ERROR(maj_stat))
1641             goto cleanup;
1642     }
1643
1644     /*
1645      * If that failed, try to get a credential with a password.
1646      */
1647     if (!GOT_CREDS(text, params)) {
1648         if (text->password == NULL) {
1649             pass_result = _plug_get_password(params->utils, &text->password,
1650                                              &text->free_password, prompt_need);
1651             if (pass_result != SASL_OK && pass_result != SASL_INTERACT) {
1652                 result = pass_result;
1653                 goto cleanup;
1654             }
1655         }
1656
1657         if (text->password != NULL) {
1658             gss_buffer_desc password_buf;
1659
1660             password_buf.length = text->password->len;
1661             password_buf.value = text->password->data;
1662
1663             maj_stat = gss_acquire_cred_with_password(&min_stat,
1664                                                       text->client_name,
1665                                                       &password_buf,
1666                                                       GSS_C_INDEFINITE,
1667                                                       &mechs,
1668                                                       GSS_C_INITIATE,
1669                                                       &text->client_creds,
1670                                                       NULL,
1671                                                       &text->lifetime);
1672             if (GSS_ERROR(maj_stat))
1673                 goto cleanup;
1674         }
1675     }
1676
1677     maj_stat = GSS_S_COMPLETE;
1678
1679 interact:
1680
1681     /* free prompts we got */
1682     if (prompt_need && *prompt_need) {
1683         params->utils->free(*prompt_need);
1684         *prompt_need = NULL;
1685     }
1686
1687     /* if there are prompts not filled in */
1688     if (user_result == SASL_INTERACT || auth_result == SASL_INTERACT ||
1689         pass_result == SASL_INTERACT) {
1690         /* make the prompt list */
1691         result =
1692             _plug_make_prompts(params->utils, prompt_need,
1693                                user_result == SASL_INTERACT ?
1694                                "Please enter your authorization name" : NULL,
1695                                NULL,
1696                                auth_result == SASL_INTERACT ?
1697                                "Please enter your authentication name" : NULL,
1698                                NULL,
1699                                pass_result == SASL_INTERACT ?
1700                                "Please enter your password" : NULL, NULL,
1701                                NULL, NULL, NULL,
1702                                NULL,
1703                                NULL, NULL);
1704         if (result == SASL_OK)
1705             result = SASL_INTERACT;
1706     }
1707
1708 cleanup:
1709     if (result == SASL_OK && maj_stat != GSS_S_COMPLETE) {
1710         sasl_gs2_seterror(text->utils, maj_stat, min_stat);
1711         result = (maj_stat & GSS_S_PROMPTING_NEEDED) ? SASL_INTERACT : SASL_FAIL;
1712     }
1713
1714     gss_release_buffer(&min_stat, &cred_authid);
1715
1716     return result;
1717 }
1718
1719 static int
1720 sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
1721                    int logonly)
1722 {
1723     OM_uint32 maj_stat, min_stat;
1724     gss_buffer_desc msg;
1725     OM_uint32 msg_ctx;
1726     int ret;
1727     char *out = NULL;
1728     unsigned int len, curlen = 0;
1729     const char prefix[] = "GS2 Error: ";
1730
1731     len = sizeof(prefix);
1732     ret = _plug_buf_alloc(utils, &out, &curlen, 256);
1733     if (ret != SASL_OK)
1734         return SASL_OK;
1735
1736     strcpy(out, prefix);
1737
1738     msg_ctx = 0;
1739     while (1) {
1740         maj_stat = gss_display_status(&min_stat, maj,
1741                                       GSS_C_GSS_CODE, GSS_C_NULL_OID,
1742                                       &msg_ctx, &msg);
1743
1744         if (GSS_ERROR(maj_stat)) {
1745             if (logonly) {
1746                 utils->log(utils->conn, SASL_LOG_FAIL,
1747                         "GS2 Failure: (could not get major error message)");
1748             } else {
1749                 utils->seterror(utils->conn, 0,
1750                                 "GS2 Failure "
1751                                 "(could not get major error message)");
1752             }
1753             utils->free(out);
1754             return SASL_OK;
1755         }
1756
1757         len += len + msg.length;
1758         ret = _plug_buf_alloc(utils, &out, &curlen, len);
1759         if (ret != SASL_OK) {
1760             utils->free(out);
1761             return SASL_OK;
1762         }
1763
1764         strcat(out, msg.value);
1765
1766         gss_release_buffer(&min_stat, &msg);
1767
1768         if (!msg_ctx)
1769             break;
1770     }
1771
1772     /* Now get the minor status */
1773
1774     len += 2;
1775     ret = _plug_buf_alloc(utils, &out, &curlen, len);
1776     if (ret != SASL_OK) {
1777         utils->free(out);
1778         return SASL_NOMEM;
1779     }
1780
1781     strcat(out, " (");
1782
1783     msg_ctx = 0;
1784     while (1) {
1785         maj_stat = gss_display_status(&min_stat, min,
1786                                       GSS_C_MECH_CODE, GSS_C_NULL_OID,
1787                                       &msg_ctx, &msg);
1788
1789         if (GSS_ERROR(maj_stat)) {
1790             if (logonly) {
1791                 utils->log(utils->conn, SASL_LOG_FAIL,
1792                         "GS2 Failure: (could not get minor error message)");
1793             } else {
1794                 utils->seterror(utils->conn, 0,
1795                                 "GS2 Failure "
1796                                 "(could not get minor error message)");
1797             }
1798             utils->free(out);
1799             return SASL_OK;
1800         }
1801
1802         len += len + msg.length;
1803
1804         ret = _plug_buf_alloc(utils, &out, &curlen, len);
1805         if (ret != SASL_OK) {
1806             utils->free(out);
1807             return SASL_NOMEM;
1808         }
1809
1810         strcat(out, msg.value);
1811
1812         gss_release_buffer(&min_stat, &msg);
1813
1814         if (!msg_ctx)
1815             break;
1816     }
1817
1818     len += 1;
1819     ret = _plug_buf_alloc(utils, &out, &curlen, len);
1820     if (ret != SASL_OK) {
1821         utils->free(out);
1822         return SASL_NOMEM;
1823     }
1824
1825     strcat(out, ")");
1826
1827     if (logonly) {
1828         utils->log(utils->conn, SASL_LOG_FAIL, out);
1829     } else {
1830         utils->seterror(utils->conn, 0, out);
1831     }
1832     utils->free(out);
1833
1834     return SASL_OK;
1835 }