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