# HG changeset patch # User Aki Tuomi # Date 1485420548 -7200 # Node ID f6d27a98506632000ad8a930b640dfc7a1792833 # Parent 9d941d959776253186541ac3462f397027a8e8c2 lib-oauth2: Add support library for OAUTH2 diff -r 9d941d959776 -r f6d27a985066 configure.ac --- a/configure.ac Tue Feb 14 18:48:51 2017 +0200 +++ b/configure.ac Thu Jan 26 10:49:08 2017 +0200 @@ -2630,7 +2630,7 @@ dnl ** Shared libraries usage dnl ** -LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-smtp/libsmtp.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' +LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-oauth2/liboauth2.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-smtp/libsmtp.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' if test "$want_shared_libs" = "yes"; then LIBDOVECOT_DEPS='$(top_builddir)/src/lib-dovecot/libdovecot.la' LIBDOVECOT="$LIBDOVECOT_DEPS \$(MODULE_LIBS)" @@ -2979,6 +2979,7 @@ src/lib-fs/Makefile src/lib-fts/Makefile src/lib-http/Makefile +src/lib-oauth2/Makefile src/lib-imap/Makefile src/lib-imap-storage/Makefile src/lib-imap-client/Makefile diff -r 9d941d959776 -r f6d27a985066 src/Makefile.am --- a/src/Makefile.am Tue Feb 14 18:48:51 2017 +0200 +++ b/src/Makefile.am Thu Jan 26 10:49:08 2017 +0200 @@ -21,7 +21,8 @@ lib-smtp \ lib-imap \ lib-imap-storage \ - lib-program-client + lib-program-client \ + lib-oauth2 SUBDIRS = \ $(LIBDOVECOT_SUBDIRS) \ diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/Makefile.am Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,23 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-http \ + -I$(top_srcdir)/src/lib-settings + +noinst_LTLIBRARIES=liboauth2.la + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = \ + oauth2.h + +noinst_HEADERS = \ + oauth2-private.h + +liboauth2_la_SOURCES = \ + oauth2.c \ + oauth2-token-validate.c \ + oauth2-introspect.c \ + oauth2-refresh.c + +check_programs = \ + oauth2-server \ + test-oauth2 diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2-introspect.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2-introspect.c Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,125 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "ioloop.h" +#include "istream.h" +#include "http-client.h" +#include "http-url.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" + +static void +oauth2_introspection_callback(struct oauth2_request *req, + struct oauth2_introspection_result *res) +{ + i_assert(res->success == (res->error == NULL)); + i_assert(req->is_callback != NULL); + oauth2_introspection_callback_t *callback = req->is_callback; + req->is_callback = NULL; + callback(res, req->is_context); + oauth2_request_free_internal(req); +} + +static void +oauth2_introspect_continue(struct oauth2_request *req, bool success, + const char *error) +{ + struct oauth2_introspection_result res; + i_zero(&res); + + res.success = success; + res.error = error; + res.fields = &req->fields; + + oauth2_introspection_callback(req, &res); +} + +static void +oauth2_introspect_response(const struct http_response *response, + struct oauth2_request *req) +{ + if (response->status / 100 != 2) { + oauth2_introspect_continue(req, FALSE, response->reason); + } else { + if (response->payload == NULL) { + oauth2_introspect_continue(req, FALSE, "Missing response body"); + return; + } + req->is = response->payload; + i_stream_ref(req->is); + req->parser = json_parser_init(req->is); + req->json_parsed_cb = oauth2_introspect_continue; + req->io = io_add_istream(req->is, oauth2_parse_json, req); + oauth2_parse_json(req); + } +} + +#undef oauth2_introspection_start +struct oauth2_request* +oauth2_introspection_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_introspection_callback_t *callback, + void *context) +{ + i_assert(oauth2_valid_token(input->token)); + + pool_t pool = pool_alloconly_create_clean("oauth2 introspection", 1024); + struct oauth2_request *req = + p_new(pool, struct oauth2_request, 1); + struct oauth2_introspection_result fail = { + .success = FALSE, + }; + struct http_url *url; + const char *error; + + req->pool = pool; + req->set = set; + req->is_callback = callback; + req->is_context = context; + + string_t *enc = t_str_new(64); + str_append(enc, req->set->introspection_url); + + if (set->introspection_mode == INTROSPECTION_MODE_GET) { + http_url_escape_param(enc, input->token); + } + + if (http_url_parse(str_c(enc), NULL, 0, pool, &url, &error) < 0) { + fail.error = t_strdup_printf("http_url_parse(%s) failed: %s", + str_c(enc), error); + oauth2_introspection_callback(req, &fail); + return req; + } + + if (set->introspection_mode == INTROSPECTION_MODE_POST) { + req->req = http_client_request_url(req->set->client, "POST", url, + oauth2_introspect_response, + req); + /* add token */ + enc = t_str_new(strlen(input->token)+6); + str_append(enc, "token="); + http_url_escape_param(enc, input->token); + http_client_request_set_payload_data(req->req, enc->data, enc->used); + } else { + req->req = http_client_request_url(req->set->client, "GET", url, + oauth2_introspect_response, + req); + } + + if (set->introspection_mode == INTROSPECTION_MODE_GET_AUTH) + http_client_request_add_header(req->req, + "Authorization", + t_strdup_printf("Bearer %s", + input->token)); + + oauth2_request_set_headers(req, input); + + http_client_request_set_timeout_msecs(req->req, + req->set->timeout_msecs); + http_client_request_submit(req->req); + + return req; +} + diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2-private.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2-private.h Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,42 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ +#ifndef OAUTH2_PRIVATE_H +#define OAUTH2_PRIVATE_H 1 + +struct oauth2_request { + pool_t pool; + + const struct oauth2_settings *set; + struct http_client_request *req; + struct json_parser *parser; + struct istream *is; + struct io *io; + + const char *username; + + void (*json_parsed_cb)(struct oauth2_request*, bool success, + const char *error); + + ARRAY_TYPE(oauth2_field) fields; + char *field_name; + + oauth2_token_validation_callback_t *tv_callback; + void *tv_context; + + oauth2_introspection_callback_t *is_callback; + void *is_context; + + oauth2_refresh_callback_t *re_callback; + void *re_context; + + /* indicates whether token is valid */ + bool valid:1; +}; + +void oauth2_request_set_headers(struct oauth2_request *req, + const struct oauth2_request_input *input); + +void oauth2_request_free_internal(struct oauth2_request *req); + +void oauth2_parse_json(struct oauth2_request *req); + +#endif diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2-refresh.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2-refresh.c Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,156 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "str.h" +#include "http-client.h" +#include "http-url.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" + +static void +oauth2_refresh_callback(struct oauth2_request *req, + struct oauth2_refresh_result *res) +{ + i_assert(res->success == (res->error == NULL)); + i_assert(req->re_callback != NULL); + oauth2_refresh_callback_t *callback = req->re_callback; + req->re_callback = NULL; + callback(res, req->re_context); + oauth2_request_free_internal(req); +} + +static bool +oauth2_refresh_field_parse(const struct oauth2_field *field, + struct oauth2_refresh_result *res) +{ + if (strcasecmp(field->name, "expires_in") == 0) { + uint32_t expires_in = 0; + if (str_to_uint32(field->value, &expires_in) < 0) { + res->success = FALSE; + res->error = t_strdup_printf( + "Malformed number '%s' in expires_in", + field->value); + return FALSE; + } else { + res->expires_at = ioloop_time + expires_in; + } + } else if (strcasecmp(field->name, "token_type") == 0) { + if (strcasecmp(field->value,"bearer") != 0) { + res->success = FALSE; + res->error = t_strdup_printf( + "Expected Bearer token, got '%s'", + field->value); + return FALSE; + } + } else if (strcasecmp(field->name, "access_token") == 0) { + /* pooled memory */ + res->bearer_token = field->value; + } + return TRUE; +} + +static void +oauth2_refresh_continue(struct oauth2_request *req, bool success, + const char *error) +{ + struct oauth2_refresh_result res; + i_zero(&res); + + res.success = success; + res.error = error; + + if (res.success) { + const struct oauth2_field *field; + /* see if we can figure out when it expires */ + array_foreach(&req->fields, field) { + if (!oauth2_refresh_field_parse(field, &res)) + break; + } + } + + res.fields = &req->fields; + + oauth2_refresh_callback(req, &res); +} + +static void +oauth2_refresh_response(const struct http_response *response, + struct oauth2_request *req) +{ + if (response->status / 100 != 2) { + oauth2_refresh_continue(req, FALSE, response->reason); + } else { + if (response->payload == NULL) { + oauth2_refresh_continue(req, FALSE, "Missing response body"); + return; + } + req->is = response->payload; + i_stream_ref(req->is); + req->parser = json_parser_init(req->is); + req->json_parsed_cb = oauth2_refresh_continue; + req->io = io_add_istream(req->is, oauth2_parse_json, req); + oauth2_parse_json(req); + } +} + +#undef oauth2_refresh_start +struct oauth2_request* +oauth2_refresh_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_refresh_callback_t *callback, + void *context) +{ + i_assert(oauth2_valid_token(input->token)); + + pool_t pool = pool_alloconly_create_clean("oauth2 refresh", 1024); + struct oauth2_request *req = + p_new(pool, struct oauth2_request, 1); + struct http_url *url; + const char *error; + struct oauth2_refresh_result fail = { + .success = FALSE + }; + + req->pool = pool; + req->set = set; + req->re_callback = callback; + req->re_context = context; + + const char *_url = req->set->refresh_url; + + if (http_url_parse(_url, NULL, 0, pool, &url, &error) < 0) { + fail.error = t_strdup_printf("http_url_parse(%s) failed: %s", + _url, error); + oauth2_refresh_callback(req, &fail); + return req; + } + + req->req = http_client_request_url(req->set->client, "POST", url, + oauth2_refresh_response, + req); + string_t *payload = str_new(req->pool, 128); + str_append(payload, "client_secret="); + http_url_escape_param(payload, req->set->client_secret); + str_append(payload, "&grant_type=refresh_token&refresh_token="); + http_url_escape_param(payload, input->token); + str_append(payload, "&client_id="); + http_url_escape_param(payload, req->set->client_id); + + struct istream *is = i_stream_create_from_string(payload); + + http_client_request_add_header(req->req, "Content-Type", + "application/x-www-form-urlencoded"); + + oauth2_request_set_headers(req, input); + + http_client_request_set_payload(req->req, is, FALSE); + i_stream_unref(&is); + http_client_request_set_timeout_msecs(req->req, + req->set->timeout_msecs); + http_client_request_submit(req->req); + + return req; +} diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2-token-validate.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2-token-validate.c Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,138 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "ioloop.h" +#include "istream.h" +#include "strnum.h" +#include "http-client.h" +#include "http-url.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" + +static void +oauth2_token_validation_callback(struct oauth2_request *req, + struct oauth2_token_validation_result *res) +{ + i_assert(res->success == (res->error == NULL)); + i_assert(req->tv_callback != NULL); + oauth2_token_validation_callback_t *callback = req->tv_callback; + req->tv_callback = NULL; + callback(res, req->tv_context); + oauth2_request_free_internal(req); +} + +static void +oauth2_token_validate_continue(struct oauth2_request *req, bool success, + const char *error) +{ + struct oauth2_token_validation_result res; + i_zero(&res); + + res.success = success; + res.error = error; + res.valid = req->valid; + + if (res.success) { + const struct oauth2_field *field; + /* see if we can figure out when it expires */ + array_foreach(&req->fields, field) { + if (strcasecmp(field->name, "expires_in") == 0) { + uint32_t expires_in = 0; + if (str_to_uint32(field->value, &expires_in) < 0) { + res.success = FALSE; + res.error = "Malformed number in expires_in"; + } else { + res.expires_at = ioloop_time + expires_in; + } + break; + } + } + } + + res.fields = &req->fields; + + oauth2_token_validation_callback(req, &res); +} + +static void +oauth2_token_validate_response(const struct http_response *response, + struct oauth2_request *req) +{ + unsigned int status_1 = response->status / 100; + + if (status_1 != 2 && status_1 != 4) { + oauth2_token_validate_continue(req, FALSE, response->reason); + } else { + if (status_1 == 2) + req->valid = TRUE; + else + req->valid = FALSE; + /* 2xx is sufficient for token validation */ + if (response->payload == NULL) { + p_array_init(&req->fields, req->pool, 1); + oauth2_token_validate_continue(req, TRUE, NULL); + return; + } + req->is = response->payload; + i_stream_ref(req->is); + req->parser = json_parser_init(req->is); + req->json_parsed_cb = oauth2_token_validate_continue; + req->io = io_add_istream(req->is, oauth2_parse_json, req); + oauth2_parse_json(req); + } +} + +#undef oauth2_token_validation_start +struct oauth2_request* +oauth2_token_validation_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_token_validation_callback_t *callback, + void *context) +{ + i_assert(oauth2_valid_token(input->token)); + + struct http_url *url; + const char *error; + struct oauth2_token_validation_result fail = { + .success = FALSE + }; + + pool_t pool = pool_alloconly_create_clean("oauth2 token_validation", 1024); + struct oauth2_request *req = + p_new(pool, struct oauth2_request, 1); + + req->pool = pool; + req->set = set; + req->tv_callback = callback; + req->tv_context = context; + + string_t *enc = t_str_new(64); + str_append(enc, req->set->tokeninfo_url); + http_url_escape_param(enc, input->token); + + if (http_url_parse(str_c(enc), NULL, 0, pool, &url, &error) < 0) { + fail.error = t_strdup_printf("http_url_parse(%s) failed: %s", + str_c(enc), error); + oauth2_token_validation_callback(req, &fail); + return req; + } + + req->req = http_client_request_url(req->set->client, "GET", url, + oauth2_token_validate_response, + req); + http_client_request_add_header(req->req, + "Authorization", + t_strdup_printf("Bearer %s", + input->token)); + + oauth2_request_set_headers(req, input); + + http_client_request_set_timeout_msecs(req->req, + req->set->timeout_msecs); + http_client_request_submit(req->req); + + return req; +} + diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2.c Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,105 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "http-client.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" +#include "safe-memset.h" + +void +oauth2_parse_json(struct oauth2_request *req) +{ + bool success; + enum json_type type; + const char *token, *error; + int ret; + + req->field_name = NULL; + + while((ret = json_parse_next(req->parser, &type, &token)) > 0) { + if (req->field_name == NULL) { + if (type != JSON_TYPE_OBJECT_KEY) break; + /* cannot use t_strdup because we might + have to read more */ + req->field_name = p_strdup(req->pool, token); + } else if (type < JSON_TYPE_STRING) { + /* this should be last allocation */ + p_free(req->pool, req->field_name); + json_parse_skip_next(req->parser); + } else { + if (!array_is_created(&req->fields)) + p_array_init(&req->fields, req->pool, 4); + struct oauth2_field *field = + array_append_space(&req->fields); + field->name = req->field_name; + req->field_name = NULL; + field->value = p_strdup(req->pool, token); + } + } + + /* read more */ + if (ret == 0) return; + + io_remove(&req->io); + + if (ret > 0) { + (void)json_parser_deinit(&req->parser, &error); + error = "Invalid response data"; + success = FALSE; + } else { + ret = json_parser_deinit(&req->parser, &error); + success = (ret == 0); + } + + i_stream_unref(&req->is); + + req->json_parsed_cb(req, success, error); +} + +void +oauth2_request_abort(struct oauth2_request **_req) +{ + struct oauth2_request *req = *_req; + *_req = NULL; + + if (req->req != NULL) + http_client_request_abort(&req->req); + oauth2_request_free_internal(req); +} + +void +oauth2_request_free_internal(struct oauth2_request *req) +{ + pool_unref(&req->pool); +} + +bool oauth2_valid_token(const char *token) +{ + if (token == NULL || *token == '\0' || strpbrk(token, "\r\n") != NULL) + return FALSE; + return TRUE; +} + +void oauth2_request_set_headers(struct oauth2_request *req, + const struct oauth2_request_input *input) +{ + if (!req->set->send_auth_headers) + return; + if (input->service != NULL) { + http_client_request_add_header(req->req, "X-Dovecot-Auth-Service", + input->service); + } + if (input->local_ip.family != 0) { + const char *addr; + if (net_ipport2str(&input->local_ip, input->local_port, &addr) == 0) + http_client_request_add_header(req->req, "X-Dovecot-Auth-Local", addr); + } + if (input->remote_ip.family != 0) { + const char *addr; + if (net_ipport2str(&input->remote_ip, input->remote_port, &addr) == 0) + http_client_request_add_header(req->req, "X-Dovecot-Auth-Remote", addr); + } +} diff -r 9d941d959776 -r f6d27a985066 src/lib-oauth2/oauth2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-oauth2/oauth2.h Thu Jan 26 10:49:08 2017 +0200 @@ -0,0 +1,110 @@ +/* Copyright (c) 2017 Dovecot authors, see the included COPYING file */ +#ifndef OAUTH2_H +#define OAUTH2_H + +#include "net.h" + +struct oauth2_request; + +struct oauth2_field { + const char *name; + const char *value; +}; + +ARRAY_DEFINE_TYPE(oauth2_field, struct oauth2_field); + +struct oauth2_settings { + struct http_client *client; + /* GET tokeninfo from this URL, token is appended to URL + http://some.host/path?access_token= */ + const char *tokeninfo_url; + /* GET more information from this URL, uses Bearer authentication */ + const char *introspection_url; + /* POST refresh here, needs refresh token and client_* settings */ + const char *refresh_url; + const char *client_id; + const char *client_secret; + enum { + INTROSPECTION_MODE_GET_AUTH, + INTROSPECTION_MODE_GET, + INTROSPECTION_MODE_POST + } introspection_mode; + unsigned int timeout_msecs; + /* Should X-Dovecot-Auth-* headers be sent */ + bool send_auth_headers; +}; + +struct oauth2_token_validation_result { + ARRAY_TYPE(oauth2_field) *fields; + const char *error; + time_t expires_at; + bool success:1; + bool valid:1; +}; + +struct oauth2_introspection_result { + ARRAY_TYPE(oauth2_field) *fields; + const char *error; + bool success:1; +}; + +struct oauth2_refresh_result { + ARRAY_TYPE(oauth2_field) *fields; + const char *bearer_token; + const char *error; + time_t expires_at; + bool success:1; +}; + +struct oauth2_request_input { + const char *token; + const char *service; + struct ip_addr local_ip, real_local_ip, remote_ip, real_remote_ip; + in_port_t local_port, real_local_port, remote_port, real_remote_port; +}; + +typedef void +oauth2_token_validation_callback_t(struct oauth2_token_validation_result*, void*); + +typedef void +oauth2_introspection_callback_t(struct oauth2_introspection_result*, void*); + +typedef void +oauth2_refresh_callback_t(struct oauth2_refresh_result*, void*); + +bool oauth2_valid_token(const char *token); + +struct oauth2_request* +oauth2_token_validation_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_token_validation_callback_t *callback, + void *context); +#define oauth2_token_validation_start(set, input, callback, context) \ + oauth2_token_validation_start(set, input + \ + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_token_validation_result*, typeof(context))), \ + (oauth2_token_validation_callback_t*)callback, (void*)context); + +struct oauth2_request* +oauth2_introspection_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_introspection_callback_t *callback, + void *context); +#define oauth2_introspection_start(set, input, callback, context) \ + oauth2_introspection_start(set, input + \ + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_introspection_result*, typeof(context))), \ + (oauth2_introspection_callback_t*)callback, (void*)context); + +struct oauth2_request* +oauth2_refresh_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_refresh_callback_t *callback, + void *context); +#define oauth2_refresh_start(set, input, callback, context) \ + oauth2_refresh_start(set, input + \ + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_refresh_result*, typeof(context))), \ + (oauth2_refresh_callback_t*)callback, (void*)context); + +/* abort without calling callback, use this to cancel the request */ +void oauth2_request_abort(struct oauth2_request **); + +#endif