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