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