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