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 }