File indexing completed on 2024-05-12 05:22:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003  *            (c) 2016 - 2018  Daniel Vrátil <dvratil@kde.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  */
0007 
0008 /**
0009  * This is a very simple and single-purpose implementation of XOAUTH2 protocol
0010  * for SASL used by KDE's KIMAP and IMAP resource in order to support OAuth
0011  * Gmail authentication.
0012  *
0013  * This plugin does not even have a server-side part, and the client-side makes
0014  * assumptions about it's use related to how KIMAP's LoginJob is implemented,
0015  * so it's unsuitable for general-purpose use.
0016  *
0017  * The plugin is called libkdexoauth2.so to avoid conflict in case anyone ever
0018  * writes a proper XOAUTH2 plugin for SASL.
0019  */
0020 
0021 #include <config.h>
0022 #include <stdio.h>
0023 #include <string.h>
0024 
0025 #include <sasl/sasl.h>
0026 #include <sasl/saslplug.h>
0027 #include <sasl/saslutil.h>
0028 
0029 #include "plugin_common.h"
0030 
0031 typedef struct client_context {
0032     char *out_buf;
0033     unsigned out_buf_len;
0034 } client_context_t;
0035 
0036 static int xoauth2_client_mech_new(void *glob_context, sasl_client_params_t *params, void **conn_context)
0037 {
0038     client_context_t *context;
0039 
0040     /* Q_UNUSED */
0041     (void)glob_context;
0042 
0043     /* holds state are in */
0044     context = params->utils->malloc(sizeof(client_context_t));
0045     if (context == NULL) {
0046         MEMERROR(params->utils);
0047         return SASL_NOMEM;
0048     }
0049 
0050     memset(context, 0, sizeof(client_context_t));
0051 
0052     *conn_context = context;
0053 
0054     return SASL_OK;
0055 }
0056 
0057 static int xoauth2_client_mech_step(void *conn_context,
0058                                     sasl_client_params_t *params,
0059                                     const char *serverin,
0060                                     unsigned serverinlen,
0061                                     sasl_interact_t **prompt_need,
0062                                     const char **clientout,
0063                                     unsigned *clientoutlen,
0064                                     sasl_out_params_t *oparams)
0065 {
0066     client_context_t *context = (client_context_t *)conn_context;
0067     const sasl_utils_t *utils = params->utils;
0068     const char *authid = NULL;
0069     const char *token = NULL;
0070     int auth_result = SASL_OK;
0071     int token_result = SASL_OK;
0072     int result;
0073     *clientout = NULL;
0074     *clientoutlen = 0;
0075 
0076     /* Q_UNUSED */
0077     (void)serverin;
0078     (void)serverinlen;
0079 
0080     if (params->props.min_ssf > params->external_ssf) {
0081         SETERROR(params->utils, "SSF requested of XOAUTH2 plugin");
0082         return SASL_TOOWEAK;
0083     }
0084 
0085     /* try to get the authid */
0086     if (oparams->authid == NULL) {
0087         fprintf(stderr, "[SASL-XOAUTH2] - Requesting authID!\n");
0088         auth_result = _plug_get_authid(utils, &authid, prompt_need);
0089         if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) {
0090             fprintf(stderr, "[SASL-XOAUTH2] - _plug_get_authid FAILED!\n");
0091             return auth_result;
0092         }
0093     }
0094 
0095     /* try to get oauth token */
0096     fprintf(stderr, "[SASL-XOAUTH2] - Requesting token!\n");
0097     /* We don't use _plug_get_password because we don't really care much about
0098        safety of the OAuth token */
0099     token_result = _plug_get_simple(utils, SASL_CB_PASS, 1, &token, prompt_need);
0100     if ((token_result != SASL_OK) && (token_result != SASL_INTERACT)) {
0101         fprintf(stderr, "[SASL-XOAUTH2] - _plug_get_simple FAILED!\n");
0102         return token_result;
0103     }
0104 
0105     /* free prompts we got */
0106     if (prompt_need && *prompt_need) {
0107         utils->free(*prompt_need);
0108         *prompt_need = NULL;
0109     }
0110 
0111     /* if there are prompts not filled in */
0112     if ((auth_result == SASL_INTERACT) || (token_result == SASL_INTERACT)) {
0113         /* make the prompt list */
0114         fprintf(stderr, "[SASL-XOAUTH2] - filling prompts!\n");
0115         result = _plug_make_prompts(utils,
0116                                     prompt_need,
0117                                     NULL,
0118                                     NULL,
0119                                     auth_result == SASL_INTERACT ? "Please enter your authentication name" : NULL,
0120                                     NULL,
0121                                     token_result == SASL_INTERACT ? "Please enter OAuth token" : NULL,
0122                                     NULL,
0123                                     NULL,
0124                                     NULL,
0125                                     NULL,
0126                                     NULL,
0127                                     NULL,
0128                                     NULL);
0129         if (result != SASL_OK) {
0130             fprintf(stderr, "[SASL-XOAUTH2] - filling prompts failed FAILED!\n");
0131             return result;
0132         }
0133 
0134         return SASL_INTERACT;
0135     }
0136 
0137     /* FIXME: Fail if username is missing too? */
0138     if (!token) {
0139         PARAMERROR(params->utils);
0140         return SASL_BADPARAM;
0141     }
0142 
0143     result = params->canon_user(utils->conn, authid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
0144     if (result != SASL_OK) {
0145         fprintf(stderr, "[SASL-XOAUTH2] - canon user FAILED!\n");
0146         return result;
0147     }
0148 
0149     /* https://developers.google.com/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism */
0150     *clientoutlen = 5 /* user=*/
0151         + ((authid && *authid) ? strlen(authid) : 0) /* %s */
0152         + 1 /* \001 */
0153         + 12 /* auth=Bearer{space} */
0154         + ((token && *token) ? strlen(token) : 0) /* %s */
0155         + 2; /* \001\001 */
0156 
0157     /* remember the extra NUL on the end for stupid clients */
0158     result = _plug_buf_alloc(params->utils, &(context->out_buf), &(context->out_buf_len), *clientoutlen + 1);
0159     if (result != SASL_OK) {
0160         fprintf(stderr, "[SASL-XOAUTH2] - _plug_buf_alloc FAILED!\n");
0161         return result;
0162     }
0163 
0164     snprintf(context->out_buf, context->out_buf_len, "user=%s\1auth=Bearer %s\1\1", authid, token);
0165 
0166     *clientout = context->out_buf;
0167 
0168     /* set oparams */
0169     oparams->doneflag = 1;
0170     oparams->mech_ssf = 0;
0171     oparams->maxoutbuf = 0;
0172     oparams->encode_context = NULL;
0173     oparams->encode = NULL;
0174     oparams->decode_context = NULL;
0175     oparams->decode = NULL;
0176     oparams->param_version = 0;
0177 
0178     return SASL_OK;
0179 }
0180 
0181 static void xoauth2_client_mech_dispose(void *conn_context, const sasl_utils_t *utils)
0182 {
0183     client_context_t *context = (client_context_t *)conn_context;
0184     if (!context) {
0185         return;
0186     }
0187 
0188     if (context->out_buf) {
0189         utils->free(context->out_buf);
0190     }
0191     utils->free(context);
0192 }
0193 
0194 static sasl_client_plug_t xoauth2_client_plugins[] = {{
0195     "XOAUTH2", /* mech_name */
0196     0, /* max_ssf */
0197     SASL_SEC_NOANONYMOUS | SASL_SEC_PASS_CREDENTIALS, /* security_flags */
0198     SASL_FEAT_WANT_CLIENT_FIRST | SASL_FEAT_ALLOWS_PROXY, /* features */
0199     NULL, /* required_prompts */
0200     NULL, /* glob_context */
0201     &xoauth2_client_mech_new, /* mech_new */
0202     &xoauth2_client_mech_step, /* mech_step */
0203     &xoauth2_client_mech_dispose, /* mech_dispose */
0204     NULL, /* mech_free */
0205     NULL, /* idle */
0206     NULL, /* spare */
0207     NULL /* spare */
0208 }};
0209 
0210 int xoauth2_client_plug_init(sasl_utils_t *utils, int maxversion, int *out_version, sasl_client_plug_t **pluglist, int *plugcount)
0211 {
0212     if (maxversion < SASL_CLIENT_PLUG_VERSION) {
0213         SETERROR(utils, "XOAUTH2 version mismatch");
0214         return SASL_BADVERS;
0215     }
0216 
0217     *out_version = SASL_CLIENT_PLUG_VERSION;
0218     *pluglist = xoauth2_client_plugins;
0219     *plugcount = 1;
0220 
0221     return SASL_OK;
0222 }