a4b0e51cf81fa4428ac0d0b3c0a7047633e43a2a
[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         if (params->gss_creds == GSS_C_NO_CREDENTIAL &&
711             text->password != NULL && text->password->len != 0) {
712             gss_buffer_desc password_buf;
713             gss_buffer_desc name_buf;
714             gss_OID_set_desc mechs;
715
716             name_buf.length = strlen(oparams->authid);
717             name_buf.value = (void *)oparams->authid;
718
719             password_buf.length = text->password->len;
720             password_buf.value = text->password->data;
721
722             mechs.count = 1;
723             mechs.elements = (gss_OID)text->mechanism;
724
725             maj_stat = gss_import_name(&min_stat,
726                                        &name_buf,
727                                        GSS_C_NT_USER_NAME,
728                                        &text->client_name);
729             if (GSS_ERROR(maj_stat))
730                 goto cleanup;
731
732             maj_stat = gss_acquire_cred_with_password(&min_stat,
733                                                       text->client_name,
734                                                       &password_buf,
735                                                       GSS_C_INDEFINITE,
736                                                       &mechs,
737                                                       GSS_C_INITIATE,
738                                                       &text->client_creds,
739                                                       NULL,
740                                                       &text->lifetime);
741             if (GSS_ERROR(maj_stat))
742                 goto cleanup;
743         }
744
745         initialContextToken = 1;
746     } else
747         initialContextToken = 0;
748
749     if (text->server_name == GSS_C_NO_NAME) { /* only once */
750         name_buf.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
751         name_buf.value = params->utils->malloc(name_buf.length + 1);
752         if (name_buf.value == NULL) {
753             ret = SASL_NOMEM;
754             goto cleanup;
755         }
756         if (params->serverFQDN == NULL ||
757             strlen(params->serverFQDN) == 0) {
758             SETERROR(text->utils, "GS2 Failure: no serverFQDN");
759             ret = SASL_FAIL;
760             goto cleanup;
761         }
762
763         snprintf(name_buf.value, name_buf.length + 1,
764                  "%s@%s", params->service, params->serverFQDN);
765
766         maj_stat = gss_import_name (&min_stat,
767                                     &name_buf,
768                                     GSS_C_NT_HOSTBASED_SERVICE,
769                                     &text->server_name);
770         params->utils->free(name_buf.value);
771         name_buf.value = NULL;
772
773         if (GSS_ERROR(maj_stat))
774             goto cleanup;
775     }
776
777     /* From GSSAPI plugin: apparently this is for some IMAP bug workaround */
778     if (serverinlen == 0 && text->gss_ctx != GSS_C_NO_CONTEXT) {
779         gss_delete_sec_context(&min_stat, &text->gss_ctx, GSS_C_NO_BUFFER);
780         text->gss_ctx = GSS_C_NO_CONTEXT;
781     }
782
783     input_token.value = (void *)serverin;
784     input_token.length = serverinlen;
785
786     if (initialContextToken) {
787         if ((text->plug.client->features & SASL_FEAT_GSS_FRAMING) == 0)
788             text->gs2_flags |= GS2_NONSTD_FLAG;
789
790         switch (params->cbindingdisp) {
791         case SASL_CB_DISP_NONE:
792             text->gs2_flags |= GS2_CB_FLAG_N;
793             break;
794         case SASL_CB_DISP_USED:
795             text->gs2_flags |= GS2_CB_FLAG_P;
796             break;
797         case SASL_CB_DISP_WANT:
798             text->gs2_flags |= GS2_CB_FLAG_Y;
799             break;
800         }
801
802         ret = gs2_make_header(text, params,
803                               strcmp(oparams->user, oparams->authid) ?
804                                      (char *) oparams->user : NULL,
805                               &text->out_buf, &text->out_buf_len);
806         if (ret != 0)
807             goto cleanup;
808     }
809
810     req_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
811
812     maj_stat = gss_init_sec_context(&min_stat,
813                                     (params->gss_creds != GSS_C_NO_CREDENTIAL)
814                                         ? (gss_cred_id_t)params->gss_creds
815                                         : text->client_creds,
816                                     &text->gss_ctx,
817                                     text->server_name,
818                                     (gss_OID)text->mechanism,
819                                     req_flags,
820                                     GSS_C_INDEFINITE,
821                                     &text->gss_cbindings,
822                                     serverinlen ? &input_token : GSS_C_NO_BUFFER,
823                                     NULL,
824                                     &output_token,
825                                     &ret_flags,
826                                     &text->lifetime);
827     if (GSS_ERROR(maj_stat))
828         goto cleanup;
829
830     ret = gs2_make_message(text, params, initialContextToken, &output_token,
831                            &text->out_buf, &text->out_buf_len);
832     if (ret != 0)
833         goto cleanup;
834
835     *clientout = text->out_buf;
836     *clientoutlen = text->out_buf_len;
837
838     if (maj_stat == GSS_S_CONTINUE_NEEDED) {
839         ret = SASL_CONTINUE;
840         goto cleanup;
841     }
842
843     if (text->client_name != GSS_C_NO_NAME)
844         gss_release_name(&min_stat, &text->client_name);
845
846     maj_stat = gss_inquire_context(&min_stat,
847                                    text->gss_ctx,
848                                    &text->client_name,
849                                    NULL,
850                                    &text->lifetime,
851                                    NULL,
852                                    &ret_flags, /* flags */
853                                    NULL,
854                                    NULL);
855     if (GSS_ERROR(maj_stat))
856         goto cleanup;
857
858     if ((ret_flags & req_flags) != req_flags) {
859         maj_stat = SASL_BADAUTH;
860         goto cleanup;
861     }
862
863     maj_stat = gss_display_name(&min_stat,
864                                 text->client_name,
865                                 &name_buf,
866                                 NULL);
867     if (GSS_ERROR(maj_stat))
868         goto cleanup;
869
870     oparams->gss_peer_name = text->server_name;
871     oparams->gss_local_name = text->client_name;
872     oparams->encode = NULL;
873     oparams->decode = NULL;
874     oparams->mech_ssf = 0;
875     oparams->maxoutbuf = 0xFFFFFF;
876     oparams->doneflag = 1;
877
878 cleanup:
879     gss_release_buffer(&min_stat, &output_token);
880     gss_release_buffer(&min_stat, &name_buf);
881
882     if (ret == SASL_OK && maj_stat != GSS_S_COMPLETE) {
883         sasl_gs2_seterror(text->utils, maj_stat, min_stat);
884         ret = SASL_FAIL;
885     }
886     if (ret != SASL_OK && ret != SASL_CONTINUE)
887         sasl_gs2_free_context_contents(text);
888
889     return ret;
890 }
891
892 static int gs2_client_mech_new(void *glob_context,
893                                sasl_client_params_t *params,
894                                void **conn_context)
895 {
896     context_t *text;
897     int ret;
898
899     text = sasl_gs2_new_context(params->utils);
900     if (text == NULL) {
901         MEMERROR(params->utils);
902         return SASL_NOMEM;
903     }
904
905     text->gss_ctx = GSS_C_NO_CONTEXT;
906     text->client_name = GSS_C_NO_NAME;
907     text->server_creds = GSS_C_NO_CREDENTIAL;
908     text->client_creds  = GSS_C_NO_CREDENTIAL;
909     text->plug.client = glob_context;
910
911     ret = gs2_map_sasl_name(params->utils, text->plug.client->mech_name,
912                             &text->mechanism);
913     if (ret != SASL_OK) {
914         gs2_common_mech_dispose(text, params->utils);
915         return ret;
916     }
917
918     *conn_context = text;
919
920     return SASL_OK;
921 }
922
923 static int
924 gs2_client_plug_alloc(const sasl_utils_t *utils,
925                       void *plug,
926                       gss_buffer_t sasl_name,
927                       gss_OID mech)
928 {
929     int ret;
930     sasl_client_plug_t *cplug = (sasl_client_plug_t *)plug;
931     gss_buffer_desc buf;
932
933     memset(cplug, 0, sizeof(*cplug));
934
935     ret = gs2_get_mech_attrs(utils, mech,
936                              &cplug->security_flags,
937                              &cplug->features,
938                              &cplug->required_prompts);
939     if (ret != SASL_OK)
940         return ret;
941
942     ret = gs2_duplicate_buffer(utils, sasl_name, &buf);
943     if (ret != SASL_OK)
944         return ret;
945
946     cplug->mech_name = (char *)buf.value;
947     cplug->features |= SASL_FEAT_NEEDSERVERFQDN;
948     cplug->glob_context = plug;
949     cplug->mech_new = gs2_client_mech_new;
950     cplug->mech_step = gs2_client_mech_step;
951     cplug->mech_dispose = gs2_common_mech_dispose;
952     cplug->mech_free = gs2_common_mech_free;
953
954     return SASL_OK;
955 }
956
957 static sasl_client_plug_t *gs2_client_plugins;
958 static int gs2_client_plugcount;
959
960 int
961 gs2_client_plug_init(const sasl_utils_t *utils,
962                      int maxversion,
963                      int *outversion,
964                      sasl_client_plug_t **pluglist,
965                      int *plugcount)
966 {
967     int ret;
968
969     *pluglist = NULL;
970     *plugcount = 0;
971
972     if (maxversion < SASL_CLIENT_PLUG_VERSION)
973         return SASL_BADVERS;
974
975     *outversion = SASL_CLIENT_PLUG_VERSION;
976
977     if (gs2_client_plugins == NULL) {
978         ret = gs2_common_plug_init(utils,
979                                    sizeof(sasl_client_plug_t),
980                                    gs2_client_plug_alloc,
981                                    (void **)&gs2_client_plugins,
982                                    &gs2_client_plugcount);
983         if (ret != SASL_OK)
984             return ret;
985     }
986
987     *pluglist = gs2_client_plugins;
988     *plugcount = gs2_client_plugcount;
989
990     return SASL_OK;
991 }
992
993 /*
994  * Copy header and application channel bindings to GSS channel bindings
995  * structure in context.
996  */
997 static int
998 gs2_save_cbindings(context_t *text,
999                    gss_buffer_t header,
1000                    const sasl_channel_binding_t *cbinding)
1001 {
1002     gss_buffer_t gss_cbindings = &text->gss_cbindings.application_data;
1003     size_t len;
1004     unsigned char *p;
1005
1006     assert(gss_cbindings->value == NULL);
1007
1008     /*
1009      * The application-data field MUST be set to the gs2-header, excluding
1010      * the initial [gs2-nonstd-flag ","] part, concatenated with, when a
1011      * gs2-cb-flag of "p" is used, the application's channel binding data.
1012      */
1013     len = header->length;
1014     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1015         assert(len > 2);
1016         len -= 2;
1017     }
1018     if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
1019         cbinding != NULL) {
1020         len += cbinding->len;
1021     }
1022
1023     gss_cbindings->length = len;
1024     gss_cbindings->value = text->utils->malloc(len);
1025     if (gss_cbindings->value == NULL)
1026         return SASL_NOMEM;
1027
1028     p = (unsigned char *)gss_cbindings->value;
1029     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1030         memcpy(p, (unsigned char *)header->value + 2, header->length - 2);
1031         p += header->length - 2;
1032     } else {
1033         memcpy(p, header->value, header->length);
1034         p += header->length;
1035     }
1036
1037     if ((text->gs2_flags & GS2_CB_FLAG_MASK) == GS2_CB_FLAG_P &&
1038         cbinding != NULL) {
1039         memcpy(p, cbinding->data, cbinding->len);
1040     }
1041
1042     return SASL_OK;
1043 }
1044
1045 #define CHECK_REMAIN(n)     do { if (remain < (n)) return SASL_BADPROT; } while (0)
1046
1047 /*
1048  * Verify gs2-header, save authzid and channel bindings to context.
1049  */
1050 static int
1051 gs2_verify_initial_message(context_t *text,
1052                            sasl_server_params_t *sparams,
1053                            const char *in,
1054                            unsigned inlen,
1055                            gss_buffer_t token)
1056 {
1057     char *p = (char *)in;
1058     unsigned remain = inlen;
1059     int ret;
1060     gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
1061
1062     assert(text->cbindingname == NULL);
1063     assert(text->authzid == NULL);
1064
1065     token->length = 0;
1066     token->value = NULL;
1067
1068     /* minimum header includes CB flag and non-zero GSS token */
1069     CHECK_REMAIN(4); /* [pny],,. */
1070
1071     /* non-standard GSS framing flag */
1072     if (remain > 1 && memcmp(p, "F,", 2) == 0) {
1073         text->gs2_flags |= GS2_NONSTD_FLAG;
1074         remain -= 2;
1075         p += 2;
1076     }
1077
1078     /* SASL channel bindings */
1079     CHECK_REMAIN(1); /* [pny] */
1080     remain--;
1081     switch (*p++) {
1082     case 'p':
1083         CHECK_REMAIN(1); /* = */
1084         remain--;
1085         if (*p++ != '=')
1086             return SASL_BADPROT;
1087
1088         ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->cbindingname);
1089         if (ret != SASL_OK)
1090             return ret;
1091
1092         text->gs2_flags |= GS2_CB_FLAG_P;
1093         break;
1094     case 'n':
1095         text->gs2_flags |= GS2_CB_FLAG_N;
1096         break;
1097     case 'y':
1098         text->gs2_flags |= GS2_CB_FLAG_Y;
1099         break;
1100     }
1101
1102     CHECK_REMAIN(1); /* , */
1103     remain--;
1104     if (*p++ != ',')
1105         return SASL_BADPROT;
1106
1107     /* authorization identity */
1108     if (remain > 1 && memcmp(p, "a=", 2) == 0) {
1109         CHECK_REMAIN(2);
1110         remain -= 2;
1111         p += 2;
1112
1113         ret = gs2_unescape_authzid(text->utils, &p, &remain, &text->authzid);
1114         if (ret != SASL_OK)
1115             return ret;
1116     }
1117
1118     /* end of header */
1119     CHECK_REMAIN(1); /* , */
1120     remain--;
1121     if (*p++ != ',')
1122         return SASL_BADPROT;
1123
1124     buf.length = inlen - remain;
1125     buf.value = (void *)in;
1126
1127     /* stash channel bindings to pass into gss_accept_sec_context() */
1128     ret = gs2_save_cbindings(text, &buf, sparams->cbinding);
1129     if (ret != SASL_OK)
1130         return ret;
1131
1132     buf.length = remain;
1133     buf.value = p;
1134
1135     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1136         token->value = text->utils->malloc(buf.length);
1137         if (token->value == NULL)
1138             return SASL_NOMEM;
1139
1140         token->length = buf.length;
1141         memcpy(token->value, buf.value, buf.length);
1142     } else {
1143         unsigned int token_size;
1144
1145         /* create a properly formed GSS token */
1146         token_size = gs2_token_size(text->mechanism, buf.length);
1147         token->value = text->utils->malloc(token_size);
1148         if (token->value == NULL)
1149             return SASL_NOMEM;
1150
1151         token->length = token_size;
1152
1153         p = (char *)token->value;
1154         gs2_make_token_header(text->mechanism, buf.length,
1155                               (unsigned char **)&p);
1156         memcpy(p, buf.value, buf.length);
1157     }
1158
1159     return SASL_OK;
1160 }
1161
1162 /*
1163  * Create gs2-header, save channel bindings to context.
1164  */
1165 static int
1166 gs2_make_header(context_t *text,
1167                 sasl_client_params_t *cparams,
1168                 const char *authzid,
1169                 char **out,
1170                 unsigned *outlen)
1171 {
1172     size_t required = 0;
1173     size_t wire_authzid_len = 0, cbnamelen = 0;
1174     char *wire_authzid = NULL;
1175     char *p;
1176     int ret;
1177     gss_buffer_desc buf;
1178
1179     *out = NULL;
1180     *outlen = 0;
1181
1182     /* non-standard GSS framing flag */
1183     if (text->gs2_flags & GS2_NONSTD_FLAG)
1184         required += 2; /* F, */
1185
1186     /* SASL channel bindings */
1187     switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
1188     case GS2_CB_FLAG_P:
1189         if (!SASL_CB_PRESENT(cparams))
1190             return SASL_BADPARAM;
1191         cbnamelen = strlen(cparams->cbinding->name);
1192         required += 1 /*=*/ + cbnamelen;
1193         /* fallthrough */
1194     case GS2_CB_FLAG_N:
1195     case GS2_CB_FLAG_Y:
1196         required += 2; /* [pny], */
1197         break;
1198     default:
1199         return SASL_BADPARAM;
1200     }
1201
1202     /* authorization identity */
1203     if (authzid != NULL) {
1204         ret = gs2_escape_authzid(text->utils, authzid,
1205                                  strlen(authzid), &wire_authzid);
1206         if (ret != SASL_OK)
1207             return ret;
1208
1209         wire_authzid_len = strlen(wire_authzid);
1210         required += 2 /* a= */ + wire_authzid_len;
1211     }
1212
1213     required += 1; /* trailing comma */
1214
1215     ret = _plug_buf_alloc(text->utils, out, outlen, required);
1216     if (ret != SASL_OK) {
1217         text->utils->free(wire_authzid);
1218         return ret;
1219     }
1220
1221     *out = text->out_buf;
1222     *outlen = required;
1223
1224     p = (char *)text->out_buf;
1225     if (text->gs2_flags & GS2_NONSTD_FLAG) {
1226         *p++ = 'F';
1227         *p++ = ',';
1228     }
1229     switch (text->gs2_flags & GS2_CB_FLAG_MASK) {
1230     case GS2_CB_FLAG_P:
1231         memcpy(p, "p=", 2);
1232         memcpy(p + 2, cparams->cbinding->name, cbnamelen);
1233         p += 2 + cbnamelen;
1234         break;
1235     case GS2_CB_FLAG_N:
1236         *p++ = 'n';
1237         break;
1238     case GS2_CB_FLAG_Y:
1239         *p++ = 'y';
1240         break;
1241     }
1242     *p++ = ',';
1243     if (wire_authzid != NULL) {
1244         memcpy(p, "a=", 2);
1245         memcpy(p + 2, wire_authzid, wire_authzid_len);
1246         text->utils->free(wire_authzid);
1247         p += 2 + wire_authzid_len;
1248     }
1249     *p++ = ',';
1250
1251     assert(p == (char *)text->out_buf + required);
1252
1253     buf.length = required;
1254     buf.value = *out;
1255
1256     ret = gs2_save_cbindings(text, &buf, cparams->cbinding);
1257     if (ret != SASL_OK)
1258         return ret;
1259
1260     return SASL_OK;
1261 }
1262
1263 /*
1264  * Convert a GSS token to a GS2 one
1265  */
1266 static int
1267 gs2_make_message(context_t *text,
1268                  sasl_client_params_t *cparams __attribute__((unused)),
1269                  int initialContextToken,
1270                  gss_buffer_t token,
1271                  char **out,
1272                  unsigned *outlen)
1273 {
1274     OM_uint32 major, minor;
1275     unsigned char *mech_token_data;
1276     size_t mech_token_size;
1277     char *p;
1278     unsigned header_len = 0;
1279     int ret;
1280
1281     mech_token_size = token->length;
1282     mech_token_data = (unsigned char *)token->value;
1283
1284     if (initialContextToken) {
1285         header_len = *outlen;
1286
1287         major = gs2_verify_token_header(&minor, text->mechanism,
1288                                         &mech_token_size, &mech_token_data,
1289                                         token->length);
1290         if ((major == GSS_S_DEFECTIVE_TOKEN &&
1291              (text->plug.client->features & SASL_FEAT_GSS_FRAMING)) ||
1292             GSS_ERROR(major))
1293             return SASL_FAIL;
1294     }
1295
1296     ret = _plug_buf_alloc(text->utils, out, outlen,
1297                           header_len + mech_token_size);
1298     if (ret != 0)
1299         return ret;
1300
1301     p = *out + header_len;
1302     memcpy(p, mech_token_data, mech_token_size);
1303
1304     *outlen = header_len + mech_token_size;
1305
1306     return SASL_OK;
1307 }
1308
1309 static const unsigned long gs2_required_prompts[] = {
1310     SASL_CB_LIST_END
1311 };
1312
1313 /*
1314  * Map GSS mechanism attributes to SASL ones
1315  */
1316 static int
1317 gs2_get_mech_attrs(const sasl_utils_t *utils,
1318                    const gss_OID mech,
1319                    unsigned int *security_flags,
1320                    unsigned int *features,
1321                    const unsigned long **prompts)
1322 {
1323     OM_uint32 major, minor;
1324     int present;
1325     gss_OID_set attrs = GSS_C_NO_OID_SET;
1326
1327     major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL);
1328     if (GSS_ERROR(major)) {
1329         utils->seterror(utils->conn, SASL_NOLOG,
1330                         "GS2 Failure: gss_inquire_attrs_for_mech");
1331         return SASL_FAIL;
1332     }
1333
1334     *security_flags = SASL_SEC_NOPLAINTEXT | SASL_SEC_NOACTIVE;
1335     *features = SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_CHANNEL_BINDING;
1336     if (prompts != NULL)
1337         *prompts = gs2_required_prompts;
1338
1339 #define MA_PRESENT(a)   (gss_test_oid_set_member(&minor, (gss_OID)(a), \
1340                                                  attrs, &present) == GSS_S_COMPLETE && \
1341                          present)
1342
1343     if (MA_PRESENT(GSS_C_MA_PFS))
1344         *security_flags |= SASL_SEC_FORWARD_SECRECY;
1345     if (!MA_PRESENT(GSS_C_MA_AUTH_INIT_ANON))
1346         *security_flags |= SASL_SEC_NOANONYMOUS;
1347     if (MA_PRESENT(GSS_C_MA_DELEG_CRED))
1348         *security_flags |= SASL_SEC_PASS_CREDENTIALS;
1349     if (MA_PRESENT(GSS_C_MA_AUTH_TARG))
1350         *security_flags |= SASL_SEC_MUTUAL_AUTH;
1351     if (MA_PRESENT(GSS_C_MA_AUTH_INIT_INIT) && prompts != NULL)
1352         *prompts = NULL;
1353     if (MA_PRESENT(GSS_C_MA_ITOK_FRAMED))
1354         *features |= SASL_FEAT_GSS_FRAMING;
1355
1356     gss_release_oid_set(&minor, &attrs);
1357
1358     return SASL_OK;
1359 }
1360
1361 /*
1362  * Enumerate GSS mechanisms that can be used for GS2
1363  */
1364 static int gs2_indicate_mechs(const sasl_utils_t *utils)
1365 {
1366     OM_uint32 major, minor;
1367     gss_OID_desc desired_oids[3];
1368     gss_OID_set_desc desired_attrs;
1369     gss_OID_desc except_oids[3];
1370     gss_OID_set_desc except_attrs;
1371
1372     if (gs2_mechs != GSS_C_NO_OID_SET)
1373         return SASL_OK;
1374
1375     desired_oids[0] = *GSS_C_MA_AUTH_INIT;
1376     desired_oids[1] = *GSS_C_MA_AUTH_TARG;
1377     desired_oids[2] = *GSS_C_MA_CBINDINGS;
1378     desired_attrs.count = sizeof(desired_oids)/sizeof(desired_oids[0]);
1379     desired_attrs.elements = desired_oids;
1380
1381     except_oids[0] = *GSS_C_MA_MECH_NEGO;
1382     except_oids[1] = *GSS_C_MA_NOT_MECH;
1383     except_oids[2] = *GSS_C_MA_DEPRECATED;
1384
1385     except_attrs.count = sizeof(except_oids)/sizeof(except_oids[0]);
1386     except_attrs.elements = except_oids;
1387
1388     major = gss_indicate_mechs_by_attrs(&minor,
1389                                         &desired_attrs,
1390                                         &except_attrs,
1391                                         GSS_C_NO_OID_SET,
1392                                         &gs2_mechs);
1393     if (GSS_ERROR(major)) {
1394         utils->seterror(utils->conn, SASL_NOLOG,
1395                         "GS2 Failure: gss_indicate_mechs_by_attrs");
1396         return SASL_FAIL;
1397     }
1398
1399     return (gs2_mechs->count > 0) ? SASL_OK : SASL_NOMECH;
1400 }
1401
1402 /*
1403  * Map SASL mechanism name to OID
1404  */
1405 static int
1406 gs2_map_sasl_name(const sasl_utils_t *utils,
1407                   const char *mech,
1408                   gss_OID *oid)
1409 {
1410     OM_uint32 major, minor;
1411     gss_buffer_desc buf;
1412
1413     buf.length = strlen(mech);
1414     buf.value = (void *)mech;
1415
1416     major = gss_inquire_mech_for_saslname(&minor, &buf, oid);
1417     if (GSS_ERROR(major)) {
1418         utils->seterror(utils->conn, SASL_NOLOG,
1419                         "GS2 Failure: gss_inquire_mech_for_saslname");
1420         return SASL_FAIL;
1421     }
1422
1423     return SASL_OK;
1424 }
1425
1426 static int
1427 gs2_duplicate_buffer(const sasl_utils_t *utils,
1428                      const gss_buffer_t src,
1429                      gss_buffer_t dst)
1430 {
1431     dst->value = utils->malloc(src->length + 1);
1432     if (dst->value == NULL)
1433         return SASL_NOMEM;
1434
1435     memcpy(dst->value, src->value, src->length);
1436     ((char *)dst->value)[src->length] = '\0';
1437     dst->length = src->length;
1438
1439     return SASL_OK;
1440 }
1441
1442 static int
1443 gs2_unescape_authzid(const sasl_utils_t *utils,
1444                      char **endp,
1445                      unsigned *remain,
1446                      char **authzid)
1447 {
1448     char *in = *endp;
1449     size_t i, len, inlen = *remain;
1450     char *p;
1451
1452     *endp = NULL;
1453
1454     for (i = 0, len = 0; i < inlen; i++) {
1455         if (in[i] == ',') {
1456             *endp = &in[i];
1457             *remain -= i;
1458             break;
1459         } else if (in[i] == '=') {
1460             if (inlen <= i + 2)
1461                 return SASL_BADPROT;
1462             i += 2;
1463         }
1464         len++;
1465     }
1466
1467     if (len == 0 || *endp == NULL)
1468         return SASL_BADPROT;
1469
1470     p = *authzid = utils->malloc(len + 1);
1471     if (*authzid == NULL)
1472         return SASL_NOMEM;
1473
1474     for (i = 0; i < inlen; i++) {
1475         if (in[i] == ',')
1476             break;
1477         else if (in[i] == '=') {
1478             if (memcmp(&in[i + 1], "2C", 2) == 0)
1479                 *p++ = ',';
1480             else if (memcmp(&in[i + 1], "3D", 2) == 0)
1481                 *p++ = '=';
1482             else {
1483                 utils->free(*authzid);
1484                 *authzid = NULL;
1485                 return SASL_BADPROT;
1486             }
1487             i += 2;
1488         } else
1489             *p++ = in[i];
1490     }
1491
1492     *p = '\0';
1493
1494     return SASL_OK;
1495 }
1496
1497 static int
1498 gs2_escape_authzid(const sasl_utils_t *utils,
1499                    const char *in,
1500                    unsigned inlen,
1501                    char **authzid)
1502 {
1503     size_t i;
1504     char *p;
1505
1506     p = *authzid = utils->malloc((inlen * 3) + 1);
1507     if (*authzid == NULL)
1508         return SASL_NOMEM;
1509
1510     for (i = 0; i < inlen; i++) {
1511         if (in[i] == ',') {
1512             memcpy(p, "=2C", 3);
1513             p += 3;
1514         } else if (in[i] == '=') {
1515             memcpy(p, "=3D", 3);
1516             p += 3;
1517         } else {
1518             *p++ = in[i];
1519         }
1520     }
1521
1522     *p = '\0';
1523
1524     return SASL_OK;
1525 }
1526
1527 static int
1528 gs2_ask_user_info(context_t *text,
1529                   sasl_client_params_t *params,
1530                   char **realms __attribute__((unused)),
1531                   int nrealm __attribute__((unused)),
1532                   sasl_interact_t **prompt_need,
1533                   sasl_out_params_t *oparams)
1534 {
1535     int result = SASL_OK;
1536     const char *authid = NULL, *userid = NULL;
1537     int user_result = SASL_OK;
1538     int auth_result = SASL_OK;
1539     int pass_result = SASL_OK;
1540
1541     /* try to get the authid */
1542     if (oparams->authid == NULL) {
1543         auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
1544
1545         if (auth_result != SASL_OK && auth_result != SASL_INTERACT) {
1546             return auth_result;
1547         }
1548     }
1549
1550     /* try to get the userid */
1551     if (oparams->user == NULL) {
1552         user_result = _plug_get_userid(params->utils, &userid, prompt_need);
1553
1554         if (user_result != SASL_OK && user_result != SASL_INTERACT) {
1555             return user_result;
1556         }
1557     }
1558
1559     /* try to get the password */
1560     if (text->password == NULL) {
1561         pass_result = _plug_get_password(params->utils, &text->password,
1562                                          &text->free_password, prompt_need);
1563         if (pass_result != SASL_OK && pass_result != SASL_INTERACT) {
1564             return pass_result;
1565         }
1566     }
1567
1568     /* free prompts we got */
1569     if (prompt_need && *prompt_need) {
1570         params->utils->free(*prompt_need);
1571         *prompt_need = NULL;
1572     }
1573
1574     /* if there are prompts not filled in */
1575     if (user_result == SASL_INTERACT || auth_result == SASL_INTERACT ||
1576         pass_result == SASL_INTERACT) {
1577
1578         /* make the prompt list */
1579         result =
1580             _plug_make_prompts(params->utils, prompt_need,
1581                                user_result == SASL_INTERACT ?
1582                                "Please enter your authorization name" : NULL,
1583                                NULL,
1584                                auth_result == SASL_INTERACT ?
1585                                "Please enter your authentication name" : NULL,
1586                                NULL,
1587                                pass_result == SASL_INTERACT ?
1588                                "Please enter your password" : NULL, NULL,
1589                                NULL, NULL, NULL,
1590                                NULL,
1591                                NULL, NULL);
1592         if (result == SASL_OK) return SASL_INTERACT;
1593
1594         return result;
1595     }
1596
1597     if (oparams->authid == NULL) {
1598         if (userid == NULL || userid[0] == '\0') {
1599             result = params->canon_user(params->utils->conn, authid, 0,
1600                                         SASL_CU_AUTHID | SASL_CU_AUTHZID,
1601                                         oparams);
1602         } else {
1603             result = params->canon_user(params->utils->conn,
1604                                         authid, 0, SASL_CU_AUTHID, oparams);
1605             if (result != SASL_OK) return result;
1606
1607             result = params->canon_user(params->utils->conn,
1608                                         userid, 0, SASL_CU_AUTHZID, oparams);
1609         }
1610         if (result != SASL_OK)
1611             return result;
1612     }
1613
1614     return result;
1615 }
1616
1617 static int
1618 sasl_gs2_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
1619                    int logonly)
1620 {
1621     OM_uint32 maj_stat, min_stat;
1622     gss_buffer_desc msg;
1623     OM_uint32 msg_ctx;
1624     int ret;
1625     char *out = NULL;
1626     unsigned int len, curlen = 0;
1627     const char prefix[] = "GSSAPI Error: ";
1628
1629     len = sizeof(prefix);
1630     ret = _plug_buf_alloc(utils, &out, &curlen, 256);
1631     if (ret != SASL_OK)
1632         return SASL_OK;
1633
1634     strcpy(out, prefix);
1635
1636     msg_ctx = 0;
1637     while (1) {
1638         maj_stat = gss_display_status(&min_stat, maj,
1639                                       GSS_C_GSS_CODE, GSS_C_NULL_OID,
1640                                       &msg_ctx, &msg);
1641
1642         if (GSS_ERROR(maj_stat)) {
1643             if (logonly) {
1644                 utils->log(utils->conn, SASL_LOG_FAIL,
1645                         "GS2 Failure: (could not get major error message)");
1646             } else {
1647                 utils->seterror(utils->conn, 0,
1648                                 "GS2 Failure "
1649                                 "(could not get major error message)");
1650             }
1651             utils->free(out);
1652             return SASL_OK;
1653         }
1654
1655         len += len + msg.length;
1656         ret = _plug_buf_alloc(utils, &out, &curlen, len);
1657         if (ret != SASL_OK) {
1658             utils->free(out);
1659             return SASL_OK;
1660         }
1661
1662         strcat(out, msg.value);
1663
1664         gss_release_buffer(&min_stat, &msg);
1665
1666         if (!msg_ctx)
1667             break;
1668     }
1669
1670     /* Now get the minor status */
1671
1672     len += 2;
1673     ret = _plug_buf_alloc(utils, &out, &curlen, len);
1674     if (ret != SASL_OK) {
1675         utils->free(out);
1676         return SASL_NOMEM;
1677     }
1678
1679     strcat(out, " (");
1680
1681     msg_ctx = 0;
1682     while (1) {
1683         maj_stat = gss_display_status(&min_stat, min,
1684                                       GSS_C_MECH_CODE, GSS_C_NULL_OID,
1685                                       &msg_ctx, &msg);
1686
1687         if (GSS_ERROR(maj_stat)) {
1688             if (logonly) {
1689                 utils->log(utils->conn, SASL_LOG_FAIL,
1690                         "GS2 Failure: (could not get minor error message)");
1691             } else {
1692                 utils->seterror(utils->conn, 0,
1693                                 "GS2 Failure "
1694                                 "(could not get minor error message)");
1695             }
1696             utils->free(out);
1697             return SASL_OK;
1698         }
1699
1700         len += len + msg.length;
1701
1702         ret = _plug_buf_alloc(utils, &out, &curlen, len);
1703         if (ret != SASL_OK) {
1704             utils->free(out);
1705             return SASL_NOMEM;
1706         }
1707
1708         strcat(out, msg.value);
1709
1710         gss_release_buffer(&min_stat, &msg);
1711
1712         if (!msg_ctx)
1713             break;
1714     }
1715
1716     len += 1;
1717     ret = _plug_buf_alloc(utils, &out, &curlen, len);
1718     if (ret != SASL_OK) {
1719         utils->free(out);
1720         return SASL_NOMEM;
1721     }
1722
1723     strcat(out, ")");
1724
1725     if (logonly) {
1726         utils->log(utils->conn, SASL_LOG_FAIL, out);
1727     } else {
1728         utils->seterror(utils->conn, 0, out);
1729     }
1730     utils->free(out);
1731
1732     return SASL_OK;
1733 }