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