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