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