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