effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "lib.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "array.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "str.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "var-expand.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "env-util.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "var-expand.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "settings.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "oauth2.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "http-client.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "iostream-ssl.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "auth-request.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "passdb.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "passdb-template.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "llist.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include "db-oauth2.h"
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#include <stddef.h>
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagherstruct passdb_oauth2_settings {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* tokeninfo endpoint, format https://endpoint/somewhere?token= */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *tokeninfo_url;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* introspection endpoint, format https://endpoint/somewhere */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *introspection_url;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* expected scope, optional */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *scope;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* mode of introspection, one of get, get-auth, post
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher - get: append token to url
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher - get-auth: send token with header Authorization: Bearer token
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher - post: send token=<token> as POST request
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher */
294e9a5521d327c5cdc49beeb9cb9e703b3134f1Jan Zeleny const char *introspection_mode;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* normalization var-expand template for username, defaults to %Lu */
4e2d9fe30bf8b692972a9654c60d2d90ed355815Stephen Gallagher const char *username_format;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* name of username attribute to lookup, mandatory */
a679f0167b646cffdae86546ed77e105576991b0Pavel Březina const char *username_attribute;
8a05fd320a44636d120a18eb7e9956c7b35b3138Jakub Hrozek /* name of account is active attribute, optional */
ba4a81e933deebb416603369b447ead6ebaa040dJakub Hrozek const char *active_attribute;
60cab26b12df9a2153823972cde0c38ca86e01b9Yassir Elley /* expected active value for active attribute, optional */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *active_value;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* template to expand into passdb */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *pass_attrs;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* TLS options */
f6cd1236c27817b97db002094b76648d92b55f82Jan Zeleny const char *tls_ca_cert_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *tls_ca_cert_dir;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *tls_cert_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *tls_key_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *tls_cipher_suite;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* HTTP rawlog directory */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *rawlog_dir;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* HTTP client options */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher unsigned int timeout_msecs;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher unsigned int max_idle_time_msecs;
4dd38025efda88f123eac672f87d3cda12f050c8Jakub Hrozek unsigned int max_parallel_connections;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher unsigned int max_pipelined_requests;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher bool tls_allow_invalid_cert;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher bool debug;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* Should introspection be done even if not necessary */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher bool force_introspection;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher bool send_auth_headers;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher};
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagherstruct db_oauth2 {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct db_oauth2 *prev,*next;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
407123c67114bf010cdad4418f291f9fb3762f4aCove Schneider pool_t pool;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *config_path;
35872dc24058c5e8028cb4082fd405a27835dcd1Jakub Hrozek struct passdb_oauth2_settings set;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct http_client *client;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct passdb_template *tmpl;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct oauth2_settings oauth2_set;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct db_oauth2_request *head;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher unsigned int refcount;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher};
35872dc24058c5e8028cb4082fd405a27835dcd1Jakub Hrozek
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagherstatic struct db_oauth2 *db_oauth2_head = NULL;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#undef DEF_STR
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#undef DEF_BOOL
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#undef DEF_INT
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#define DEF_STR(name) DEF_STRUCT_STR(name, passdb_oauth2_settings)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, passdb_oauth2_settings)
07b7b76d7cd494cbd26263503ba2732c21819941Jan Zeleny#define DEF_INT(name) DEF_STRUCT_INT(name, passdb_oauth2_settings)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
e82832a64fd456d1541ce0ea3902bcfb05e69642Stephen Gallagherstatic struct setting_def setting_defs[] = {
14452cd066b51e32ca0ebad6c45ae909a1debe57Jakub Hrozek DEF_STR(tokeninfo_url),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(introspection_url),
35872dc24058c5e8028cb4082fd405a27835dcd1Jakub Hrozek DEF_STR(scope),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_BOOL(force_introspection),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(introspection_mode),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(username_format),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(username_attribute),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(pass_attrs),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(active_attribute),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(active_value),
a4cce2c98eedecb5d3b47da62104634cae268434Stephen Gallagher DEF_INT(timeout_msecs),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_INT(max_idle_time_msecs),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_INT(max_parallel_connections),
f6cd1236c27817b97db002094b76648d92b55f82Jan Zeleny DEF_INT(max_pipelined_requests),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_BOOL(send_auth_headers),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(tls_ca_cert_file),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(tls_ca_cert_dir),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(tls_cert_file),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(tls_key_file),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(tls_cipher_suite),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_BOOL(tls_allow_invalid_cert),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_STR(rawlog_dir),
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DEF_BOOL(debug),
ceb40cb8846ff755f841466908954087f927eae7Jakub Hrozek
67ca9e7c006d8619f446c018eabf29eab1368ba5Ondrej Kos { 0, NULL, 0 }
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher};
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagherstatic struct passdb_oauth2_settings default_oauth2_settings = {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .tokeninfo_url = "",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .introspection_url = "",
ae5381b3a81ed4dee51e3ac56ddabd0bf7641c86Jakub Hrozek .scope = "",
69994add9cd4e57d40b3b7a0b1783ef2d0aa974cPavel Reichl .force_introspection = FALSE,
fae99bfe4bfc8b4a12e9c2a0ad01b3684c22f934Simo Sorce .introspection_mode = "",
6263578b03a52b3ec3a2e33e097554241780fc20Lukas Slebodnik .username_format = "%Lu",
eceefd520802efe356d413a13247c5f68d8e27c8Sumit Bose .username_attribute = "email",
eceefd520802efe356d413a13247c5f68d8e27c8Sumit Bose .active_attribute = "",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .active_value = "",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .pass_attrs = "",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .rawlog_dir = "",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .timeout_msecs = 0,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .max_idle_time_msecs = 60000,
07b7b76d7cd494cbd26263503ba2732c21819941Jan Zeleny .max_parallel_connections = 1,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .max_pipelined_requests = 1,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .tls_ca_cert_file = NULL,
dcc6877aa2e2dd63a9dc9c411a9c58feaeb36b9aStephen Gallagher .tls_ca_cert_dir = NULL,
4f2e932acd5266e9d4e3f55966baafbdbd2ae210Ondrej Kos .tls_cert_file = NULL,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .tls_key_file = NULL,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .tls_cipher_suite = "HIGH:!SSLv2",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .tls_allow_invalid_cert = FALSE,
07b7b76d7cd494cbd26263503ba2732c21819941Jan Zeleny .send_auth_headers = FALSE,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher .debug = FALSE,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher};
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
1b171c456ff901ab622e44bcfd213f7de86fd787Ariel Barriastatic const char *parse_setting(const char *key, const char *value,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct db_oauth2 *db)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher{
e82832a64fd456d1541ce0ea3902bcfb05e69642Stephen Gallagher return parse_setting_from_defs(db->pool, setting_defs,
edaa983d094c239c3e1ba667bcd20ed3934be3b8Sumit Bose &db->set, key, value);
14452cd066b51e32ca0ebad6c45ae909a1debe57Jakub Hrozek}
2c29d0fdc26e0b217ed96248852779cafb2367c9Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagherstruct db_oauth2 *db_oauth2_init(const char *config_path)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher{
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct db_oauth2 *db;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher const char *error;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct ssl_iostream_settings ssl_set;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct http_client_settings http_set;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher for(db = db_oauth2_head; db != NULL; db = db->next) {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (strcmp(db->config_path, config_path) == 0) {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->refcount++;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher return db;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher }
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher }
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher pool_t pool = pool_alloconly_create("db_oauth2", 128);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db = p_new(pool, struct db_oauth2, 1);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->pool = pool;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->refcount = 1;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->config_path = p_strdup(pool, config_path);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->set = default_oauth2_settings;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (!settings_read_nosection(config_path, parse_setting, db, &error))
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_fatal("oauth2 %s: %s", config_path, error);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->tmpl = passdb_template_build(pool, db->set.pass_attrs);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_zero(&ssl_set);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_zero(&http_set);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.cipher_list = db->set.tls_cipher_suite;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.ca_file = db->set.tls_ca_cert_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.ca_dir = db->set.tls_ca_cert_dir;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (db->set.tls_cert_file != NULL && *db->set.tls_cert_file != '\0') {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.cert.cert = db->set.tls_cert_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.cert.key = db->set.tls_key_file;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher }
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.prefer_server_ciphers = TRUE;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.allow_invalid_cert = db->set.tls_allow_invalid_cert;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.verbose = db->set.debug;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher ssl_set.verbose_invalid_cert = db->set.debug;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.ssl = &ssl_set;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.dns_client_socket_path = "dns-client";
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.user_agent = "dovecot-oauth2-passdb/" DOVECOT_VERSION;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (*db->set.tokeninfo_url == '\0' && *db->set.introspection_url == '\0')
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_fatal("oauth2: Tokeninfo or introspection URL must be given");
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (*db->set.rawlog_dir != '\0')
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.rawlog_dir = db->set.rawlog_dir;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.max_idle_time_msecs = db->set.max_idle_time_msecs;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.max_parallel_connections = db->set.max_parallel_connections;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.max_pipelined_requests = db->set.max_pipelined_requests;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.no_auto_redirect = FALSE;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.no_auto_retry = TRUE;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher http_set.debug = db->set.debug;
022456e93c9b175ce3774afe524e3926f41ba80fSumit Bose
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->client = http_client_init(&http_set);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_zero(&db->oauth2_set);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.client = db->client;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.tokeninfo_url = db->set.tokeninfo_url,
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.introspection_url = db->set.introspection_url;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.timeout_msecs = db->set.timeout_msecs;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.send_auth_headers = db->set.send_auth_headers;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher if (*db->set.introspection_mode == '\0' ||
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher strcmp(db->set.introspection_mode, "auth") == 0) {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET_AUTH;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher } else if (strcmp(db->set.introspection_mode, "get") == 0) {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher } else if (strcmp(db->set.introspection_mode, "post") == 0) {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->oauth2_set.introspection_mode = INTROSPECTION_MODE_POST;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher } else {
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_fatal("Invalid value '%s' for introspection mode, must be on auth, get or post",
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->set.introspection_mode);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher }
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher DLLIST_PREPEND(&db_oauth2_head, db);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher return db;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher}
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallaghervoid db_oauth2_ref(struct db_oauth2 *db)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher{
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_assert(db->refcount > 0);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher db->refcount++;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher}
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallaghervoid db_oauth2_unref(struct db_oauth2 **_db)
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher{
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher struct db_oauth2 *ptr, *db = *_db;
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher i_assert(db->refcount > 0);
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek
ad1be6fd04234f61f108773ff39aa7485abda47cJakub Hrozek if (--db->refcount > 0) return;
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek for(ptr = db_oauth2_head; ptr != NULL; ptr = db->next) {
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek if (ptr == db) {
35872dc24058c5e8028cb4082fd405a27835dcd1Jakub Hrozek DLLIST_REMOVE(&db_oauth2_head, ptr);
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek break;
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek }
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek }
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek
74e95cfd9d3939dfe9417d79d2f6fc79b361405fJakub Hrozek i_assert(ptr != NULL && ptr == db);
effcbdb12c7ef892f1fd92a745cb33a08ca4ba30Stephen Gallagher
/* make sure all requests are aborted */
while (db->head != NULL)
oauth2_request_abort(&db->head->req);
http_client_deinit(&db->client);
pool_unref(&db->pool);
}
static bool
db_oauth2_have_all_fields(struct db_oauth2_request *req)
{
unsigned int n,i;
unsigned int size,idx;
const char *const *args = passdb_template_get_args(req->db->tmpl, &n);
if (req->fields == NULL)
return FALSE;
for(i=1;i<n;i+=2) {
const char *ptr = args[i];
while(ptr != NULL) {
ptr = strchr(ptr, '%');
if (ptr != NULL) {
const char *field;
ptr++;
var_get_key_range(ptr, &idx, &size);
ptr = ptr+idx;
field = t_strndup(ptr,size);
if (strncmp(field, "oauth2:", 8) == 0 &&
!auth_fields_exists(req->fields, ptr+8))
return FALSE;
ptr = ptr+size;
}
}
}
if (!auth_fields_exists(req->fields, req->db->set.username_attribute))
return FALSE;
if (*req->db->set.active_attribute != '\0' && !auth_fields_exists(req->fields, req->db->set.active_attribute))
return FALSE;
return TRUE;
}
static const char *field_get_default(const char *data)
{
const char *p;
p = strchr(data, ':');
if (p == NULL)
return "";
else {
/* default value given */
return p+1;
}
}
static int db_oauth2_var_expand_func_oauth2(const char *data, void *context,
const char **value_r,
const char **error_r ATTR_UNUSED)
{
struct db_oauth2_request *ctx = context;
const char *field_name = t_strcut(data, ':');
const char *value = NULL;
if (ctx->fields != NULL)
value = auth_fields_find(ctx->fields, field_name);
*value_r = value != NULL ? value : field_get_default(data);
return 1;
}
static const char *escape_none(const char *value, const struct auth_request *req ATTR_UNUSED)
{
return value;
}
static const struct var_expand_table *
db_oauth2_value_get_var_expand_table(struct auth_request *auth_request,
const char *oauth2_value)
{
struct var_expand_table *table;
unsigned int count = 1;
table = auth_request_get_var_expand_table_full(auth_request, NULL,
&count);
table[0].key = '$';
table[0].value = oauth2_value;
return table;
}
static bool
db_oauth2_template_export(struct db_oauth2_request *req,
enum passdb_result *result_r, const char **error_r)
{
/* var=$ expands into var=${oauth2:var} */
const struct var_expand_func_table funcs_table[] = {
{ "oauth2", db_oauth2_var_expand_func_oauth2 },
{ NULL, NULL }
};
string_t *dest;
const char *const *args, *value;
struct passdb_template *tmpl = req->db->tmpl;
unsigned int i, count;
if (passdb_template_is_empty(tmpl))
return TRUE;
dest = t_str_new(256);
args = passdb_template_get_args(tmpl, &count);
i_assert((count % 2) == 0);
for (i = 0; i < count; i += 2) {
if (args[i+1] == NULL)
value = "";
else {
str_truncate(dest, 0);
const struct var_expand_table *
table = db_oauth2_value_get_var_expand_table(req->auth_request,
auth_fields_find(req->fields, args[i]));
if (var_expand_with_funcs(dest, args[i+1], table, funcs_table,
req, error_r) < 0) {
*result_r = PASSDB_RESULT_INTERNAL_FAILURE;
return FALSE;
}
value = str_c(dest);
}
auth_request_set_field(req->auth_request, args[i], value,
STATIC_PASS_SCHEME);
}
return TRUE;
}
static void db_oauth2_fields_merge(struct db_oauth2_request *req,
ARRAY_TYPE(oauth2_field) *fields)
{
const struct oauth2_field *field;
if (req->fields == NULL)
req->fields = auth_fields_init(req->pool);
array_foreach(fields, field) {
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Processing field %s",
field->name);
auth_fields_add(req->fields, field->name, field->value, 0);
}
}
static void db_oauth2_callback(struct db_oauth2_request *req,
enum passdb_result result,
const char *error)
{
db_oauth2_lookup_callback_t *callback = req->callback;
req->callback = NULL;
i_assert(result == PASSDB_RESULT_OK || error != NULL);
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: callback(%d, %s)",
result, error);
if (callback != NULL) {
DLLIST_REMOVE(&req->db->head, req);
callback(req, result, error, req->context);
}
}
static bool
db_oauth2_validate_username(struct db_oauth2_request *req,
enum passdb_result *result_r, const char **error_r)
{
const char *error;
struct var_expand_table table[] = {
{ 'u', NULL, "user" },
{ 'n', NULL, "username" },
{ 'd', NULL, "domain" },
{ '\0', NULL, NULL }
};
const char *username_value =
auth_fields_find(req->fields, req->db->set.username_attribute);
if (username_value == NULL) {
*result_r = PASSDB_RESULT_INTERNAL_FAILURE;
*error_r = "No username returned";
return FALSE;
}
table[0].value = username_value;
table[1].value = t_strcut(username_value, '@');
table[2].value = i_strchr_to_next(username_value, '@');
string_t *username_req = t_str_new(32);
string_t *username_val = t_str_new(strlen(username_value));
if (auth_request_var_expand(username_req, req->db->set.username_format, req->auth_request, escape_none, &error) < 0 ||
var_expand(username_val, req->db->set.username_format, table, &error) < 0) {
*error_r = t_strdup_printf("var_expand(%s) failed: %s",
req->db->set.username_format, error);
*result_r = PASSDB_RESULT_INTERNAL_FAILURE;
return FALSE;
} else if (!str_equals(username_req, username_val)) {
*error_r = t_strdup_printf("Username '%s' did not match '%s'",
str_c(username_req), str_c(username_val));
*result_r = PASSDB_RESULT_USER_UNKNOWN;
return FALSE;
} else {
return TRUE;
}
}
static bool
db_oauth2_user_is_enabled(struct db_oauth2_request *req,
enum passdb_result *result_r, const char **error_r)
{
if (*req->db->set.active_attribute != '\0') {
const char *active_value = auth_fields_find(req->fields, req->db->set.active_attribute);
if (active_value == NULL ||
(*req->db->set.active_value != '\0' &&
strcmp(req->db->set.active_value, active_value) != 0)) {
*error_r = "User account is not active";
*result_r = PASSDB_RESULT_USER_DISABLED;
return FALSE;
}
}
return TRUE;
}
static bool
db_oauth2_token_in_scope(struct db_oauth2_request *req,
enum passdb_result *result_r, const char **error_r)
{
if (*req->db->set.scope != '\0') {
bool found = FALSE;
const char *value = auth_fields_find(req->fields, "scope");
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Token scope(s): %s",
value);
if (value != NULL) {
const char **scopes = t_strsplit_spaces(value, " ");
found = str_array_find(scopes, req->db->set.scope);
}
if (!found) {
*error_r = t_strdup_printf("Token is not valid for scope '%s'",
req->db->set.scope);
*result_r = PASSDB_RESULT_USER_DISABLED;
return FALSE;
}
}
return TRUE;
}
static void db_oauth2_process_fields(struct db_oauth2_request *req,
enum passdb_result *result_r,
const char **error_r)
{
*error_r = NULL;
if (db_oauth2_validate_username(req, result_r, error_r) &&
db_oauth2_user_is_enabled(req, result_r, error_r) &&
db_oauth2_token_in_scope(req, result_r, error_r) &&
db_oauth2_template_export(req, result_r, error_r)) {
*result_r = PASSDB_RESULT_OK;
} else {
i_assert(*result_r != PASSDB_RESULT_OK && *error_r != NULL);
}
}
static void
db_oauth2_introspect_continue(struct oauth2_introspection_result *result,
struct db_oauth2_request *req)
{
enum passdb_result passdb_result;
const char *error;
req->req = NULL;
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Introspection result: %s",
result->success ? "success" : "failed");
if (!result->success) {
/* fail here */
passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
error = result->error;
} else {
db_oauth2_fields_merge(req, result->fields);
db_oauth2_process_fields(req, &passdb_result, &error);
}
db_oauth2_callback(req, passdb_result, error);
}
static void db_oauth2_lookup_introspect(struct db_oauth2_request *req)
{
struct oauth2_request_input input;
i_zero(&input);
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Making introspection request to %s",
req->db->set.introspection_url);
input.token = req->token;
input.local_ip = req->auth_request->local_ip;
input.local_port = req->auth_request->local_port;
input.remote_ip = req->auth_request->remote_ip;
input.remote_port = req->auth_request->remote_port;
input.real_local_ip = req->auth_request->real_local_ip;
input.real_local_port = req->auth_request->real_local_port;
input.real_remote_ip = req->auth_request->real_remote_ip;
input.real_remote_port = req->auth_request->real_remote_port;
input.service = req->auth_request->service;
req->req = oauth2_introspection_start(&req->db->oauth2_set, &input,
db_oauth2_introspect_continue, req);
}
static void
db_oauth2_lookup_continue(struct oauth2_token_validation_result *result,
struct db_oauth2_request *req)
{
enum passdb_result passdb_result;
const char *error;
req->req = NULL;
if (!result->success) {
passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
error = result->error;
} else if (!result->valid) {
passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
error = "Invalid token";
} else {
db_oauth2_fields_merge(req, result->fields);
if (*req->db->set.introspection_url != '\0' &&
(req->db->set.force_introspection ||
!db_oauth2_have_all_fields(req))) {
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Introspection needed after token validation");
db_oauth2_lookup_introspect(req);
return;
}
db_oauth2_process_fields(req, &passdb_result, &error);
}
db_oauth2_callback(req, passdb_result, error);
}
#undef db_oauth2_lookup
void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req,
const char *token, struct auth_request *request,
db_oauth2_lookup_callback_t *callback, void *context)
{
struct oauth2_request_input input;
i_zero(&input);
req->db = db;
req->token = p_strdup(req->pool, token);
req->callback = callback;
req->context = context;
req->auth_request = request;
input.token = token;
input.local_ip = req->auth_request->local_ip;
input.local_port = req->auth_request->local_port;
input.remote_ip = req->auth_request->remote_ip;
input.remote_port = req->auth_request->remote_port;
input.real_local_ip = req->auth_request->real_local_ip;
input.real_local_port = req->auth_request->real_local_port;
input.real_remote_ip = req->auth_request->real_remote_ip;
input.real_remote_port = req->auth_request->real_remote_port;
input.service = req->auth_request->service;
if (*db->oauth2_set.tokeninfo_url == '\0') {
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Making introspection request to %s",
db->set.introspection_url);
req->req = oauth2_introspection_start(&req->db->oauth2_set, &input,
db_oauth2_introspect_continue, req);
} else {
if (req->auth_request->debug)
auth_request_log_debug(req->auth_request, AUTH_SUBSYS_DB,
"oauth2: Making token validation lookup to %s",
db->oauth2_set.tokeninfo_url);
req->req = oauth2_token_validation_start(&db->oauth2_set, &input,
db_oauth2_lookup_continue, req);
}
DLLIST_PREPEND(&db->head, req);
}