Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: dict_mongodb.c,v 1.3 2026/05/09 18:49:16 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	dict_mongodb 3
      6 /* SUMMARY
      7 /*	dictionary interface to mongodb, compatible with libmongoc-1.0
      8 /* SYNOPSIS
      9 /*	#include <dict_mongodb.h>
     10 /*
     11 /*	DICT *dict_mongodb_open(name, open_flags, dict_flags)
     12 /*	const char *name;
     13 /*	int	open_flags;
     14 /*	int	dict_flags;
     15 /* DESCRIPTION
     16 /*	dict_mongodb_open() opens a MongoDB database, providing a
     17 /*	dictionary interface for Postfix mappings. The result is a
     18 /*	pointer to the installed dictionary.
     19 /*
     20 /*	Configuration parameters are described in mongodb_table(5).
     21 /*
     22 /*	Arguments:
     23 /* .IP name
     24 /*	Either the path to the MongoDB configuration file (if it
     25 /*	starts with '/' or '.'), or the prefix which will be used
     26 /*	to obtain main.cf configuration parameters for this search.
     27 /*
     28 /*	In the first case, configuration parameters are specified
     29 /*	in the file as \fIname\fR=\fIvalue\fR pairs.
     30 /*
     31 /*	In the second case, the configuration parameters are prefixed
     32 /*	with the value of \fIname\fR and an underscore, and they
     33 /*	are specified in main.cf. For example, if this value is
     34 /*	\fImongodbconf\fR, the parameters would look like
     35 /*	\fImongodbconf_uri\fR, \fImongodbconf_collection\fR, and
     36 /*	so on.
     37 /* .IP open_flags
     38 /*	Must be O_RDONLY
     39 /* .IP dict_flags
     40 /*	See dict_open(3).
     41 /* SEE ALSO
     42 /*	dict(3) generic dictionary manager
     43 /* HISTORY
     44 /* .ad
     45 /* .fi
     46 /*	MongoDB support was added in Postfix 3.9.
     47 /* AUTHOR(S)
     48 /*	Hamid Maadani (hamid (at) dexo.tech)
     49 /*	Dextrous Technologies, LLC
     50 /*
     51 /*	Edited by:
     52 /*	Wietse Venema
     53 /*	porcupine.org
     54 /*
     55 /*	Based on prior work by:
     56 /*	Stephan Ferraro
     57 /*	Aionda GmbH
     58 /*--*/
     59 
     60  /*
     61   * System library.
     62   */
     63 #include <sys_defs.h>
     64 #ifdef HAS_MONGODB
     65 #include <stdio.h>
     66 #include <string.h>
     67 #include <stdlib.h>
     68 #include <errno.h>
     69 #include <ctype.h>
     70 #include <inttypes.h>			/* C99 PRId64 */
     71 
     72 #include <bson/bson.h>
     73 #include <mongoc/mongoc.h>
     74 
     75  /*
     76   * Utility library.
     77   */
     78 #include <dict.h>
     79 #include <msg.h>
     80 #include <mymalloc.h>
     81 #include <vstring.h>
     82 #include <stringops.h>
     83 #include <auto_clnt.h>
     84 #include <vstream.h>
     85 
     86  /*
     87   * Global library.
     88   */
     89 #include <cfg_parser.h>
     90 #include <db_common.h>
     91 
     92  /*
     93   * Application-specific.
     94   */
     95 #include <dict_mongodb.h>
     96 
     97  /*
     98   * Initial size for dynamically-allocated buffers.
     99   */
    100 #ifndef BUFFER_SIZE
    101 #define BUFFER_SIZE 1024
    102 #endif
    103 
    104 #define INIT_VSTR(buf, len) do { \
    105 	if (buf == 0) \
    106 		buf = vstring_alloc(len); \
    107 	VSTRING_RESET(buf); \
    108 	VSTRING_TERMINATE(buf); \
    109     } while (0)
    110 
    111 /* Structure of one mongodb dictionary handle. */
    112 typedef struct {
    113     /* Initialized by dict_mongodb_open(). */
    114     DICT    dict;			/* Parent class */
    115     CFG_PARSER *parser;			/* Configuration file parser */
    116     mongoc_client_t *client;		/* Mongo C client handle */
    117     /* Initialized by mongodb_parse_config(). */
    118     char   *uri;			/* mongodb+srv:/*localhost:27017 */
    119     char   *dbname;			/* Database name */
    120     char   *collection;			/* Collection name */
    121     char   *query_filter;		/* db_common_expand() query template */
    122     char   *projection;			/* Advanced MongoDB projection */
    123     char   *result_attribute;		/* The key(s) to return the data for */
    124     char   *result_format;		/* db_common_expand() result_template */
    125     int     expansion_limit;		/* Result expansion limit */
    126     void   *ctx;			/* db_common handle */
    127 } DICT_MONGODB;
    128 
    129 /* Per-process initialization. */
    130 static bool init_done = false;
    131 
    132 /* itoa - int64_t to string */
    133 
    134 static char *itoa(int64_t val)
    135 {
    136     static char buf[21] = {0};
    137     int     ret;
    138 
    139     /*
    140      * XXX(Wietse) replaced custom code with standard library calls that
    141      * handle zero, and negative values.
    142      */
    143 #define PRId64_FORMAT "%" PRId64
    144 
    145     ret = snprintf(buf, sizeof(buf), PRId64_FORMAT, val);
    146     if (ret < 0)
    147 	msg_panic("itoa: output error for '%s'", PRId64_FORMAT);
    148     if (ret >= sizeof(buf))
    149 	msg_panic("itoa: output for '%s' exceeds space %ld",
    150 		  PRId64_FORMAT, sizeof(buf));
    151     return (buf);
    152 }
    153 
    154 /* mongodb_parse_config - parse mongodb configuration file */
    155 
    156 static void mongodb_parse_config(DICT_MONGODB *dict_mongodb,
    157 				         const char *mongodbcf)
    158 {
    159     CFG_PARSER *p = dict_mongodb->parser;
    160 
    161     /*
    162      * Parse the configuration file.
    163      */
    164     dict_mongodb->uri = cfg_get_str(p, "uri", NULL, 1, 0);
    165     dict_mongodb->dbname = cfg_get_str(p, "dbname", NULL, 1, 0);
    166     dict_mongodb->collection = cfg_get_str(p, "collection", NULL, 1, 0);
    167     dict_mongodb->query_filter = cfg_get_str(p, "query_filter", NULL, 1, 0);
    168 
    169     /*
    170      * One of projection and result_attribute must be specified. That is
    171      * enforced in the caller.
    172      */
    173     dict_mongodb->projection = cfg_get_str(p, "projection", NULL, 0, 0);
    174     dict_mongodb->result_attribute
    175 	= cfg_get_str(p, "result_attribute", NULL, 0, 0);
    176     dict_mongodb->result_format
    177 	= cfg_get_str(dict_mongodb->parser, "result_format", "%s", 1, 0);
    178     dict_mongodb->expansion_limit
    179 	= cfg_get_int(dict_mongodb->parser, "expansion_limit", 10, 0, 100);
    180 
    181     /*
    182      * db_common query parsing and domain pattern lookup.
    183      */
    184     dict_mongodb->ctx = 0;
    185     (void) db_common_parse(&dict_mongodb->dict, &dict_mongodb->ctx,
    186 			   dict_mongodb->query_filter, 1);
    187     db_common_parse_domain(dict_mongodb->parser, dict_mongodb->ctx);
    188 }
    189 
    190 /* expand_value - expand lookup result value */
    191 
    192 static bool expand_value(DICT_MONGODB *dict_mongodb, const char *p,
    193 			         const char *lookup_name,
    194 			         VSTRING *resultString,
    195 			         int *expansion, const char *key)
    196 {
    197 
    198     /*
    199      * If a lookup result cannot be processed due to an expansion limit
    200      * error, return a DICT_ERR_RETRY error code and a 'false' result value.
    201      * As documented for many dict_xxx() implementations, and expansion limit
    202      * error is considered a temporary error.
    203      */
    204     if (dict_mongodb->expansion_limit > 0
    205 	&& ++(*expansion) > dict_mongodb->expansion_limit) {
    206 	msg_warn("%s:%s: expansion limit exceeded for key: '%s'",
    207 		 dict_mongodb->dict.type, dict_mongodb->dict.name, key);
    208 	dict_mongodb->dict.error = DICT_ERR_RETRY;
    209 	return (false);
    210     }
    211 
    212     /*
    213      * XXX(Wietse) Added the dict_mongodb_lookup() lookup_name argument,
    214      * because it selects code paths inside db_common_expand() that are
    215      * specifically for lookup results instead of lookup keys, including
    216      * %[SUD] substitution.
    217      */
    218     db_common_expand(dict_mongodb->ctx, dict_mongodb->result_format, p,
    219 		     lookup_name, resultString, 0);
    220     return (true);
    221 }
    222 
    223 /* get_result_string - convert lookup result to string, or set dict.error */
    224 
    225 static char *get_result_string(DICT_MONGODB *dict_mongodb,
    226 			               VSTRING *resultString,
    227 			               bson_iter_t *iter,
    228 			               const char *lookup_name,
    229 			               int *expansion,
    230 			               const char *key)
    231 {
    232     char   *p = NULL;
    233     bool    got_one_result = false;
    234 
    235     /*
    236      * If a lookup result cannot be processed due to an error, return a
    237      * non-zero error code and a NULL result value.
    238      */
    239     INIT_VSTR(resultString, BUFFER_SIZE);
    240     while (dict_mongodb->dict.error == DICT_ERR_NONE && bson_iter_next(iter)) {
    241 	switch (bson_iter_type(iter)) {
    242 	case BSON_TYPE_UTF8:
    243 	    p = (char *) bson_iter_utf8(iter, NULL);
    244 	    if (!bson_utf8_validate(p, strlen(p), true)) {
    245 		msg_warn("%s:%s: invalid UTF-8 in lookup result '%s'",
    246 		       dict_mongodb->dict.type, dict_mongodb->dict.name, p);
    247 		dict_mongodb->dict.error = DICT_ERR_RETRY;
    248 		break;
    249 	    }
    250 	    got_one_result |= expand_value(dict_mongodb, p, lookup_name,
    251 					   resultString, expansion, key);
    252 	    break;
    253 	case BSON_TYPE_INT64:
    254 	case BSON_TYPE_INT32:
    255 	    p = itoa(bson_iter_as_int64(iter));
    256 	    got_one_result |= expand_value(dict_mongodb, p, lookup_name,
    257 					   resultString, expansion, key);
    258 	    break;
    259 	case BSON_TYPE_ARRAY:
    260 	    ;					/* For pre-C23 Clang. */
    261 	    const uint8_t *dataBuffer = NULL;
    262 	    unsigned int len = 0;
    263 	    bson_iter_t dataIter;
    264 	    bson_t *data = NULL;
    265 
    266 	    /*
    267 	     * XXX(Wietse) are there any non-error cases, such as a valid but
    268 	     * empty array, where bson_new_from_data() or bson_iter_init()
    269 	     * would return null or false? If there are no such cases then we
    270 	     * must handle null/false as an error.
    271 	     */
    272 	    bson_iter_array(iter, &len, &dataBuffer);
    273 	    if ((data = bson_new_from_data(dataBuffer, len)) != 0
    274 		&& bson_iter_init(&dataIter, data)) {
    275 		VSTRING *iterResult = vstring_alloc(BUFFER_SIZE);
    276 
    277 		if ((p = get_result_string(dict_mongodb, iterResult, &dataIter,
    278 				       lookup_name, expansion, key)) != 0) {
    279 		    vstring_sprintf_append(resultString, (got_one_result) ?
    280 					   ",%s" : "%s", p);
    281 		    got_one_result |= true;
    282 		}
    283 		vstring_free(iterResult);
    284 	    }
    285 	    bson_destroy(data);
    286 	    break;
    287 	default:
    288 	    /* Unexpected field type. As documented, warn and ignore. */
    289 	    msg_warn("%s:%s: failed to retrieve value of '%s', "
    290 		     "Unknown result type %d.", dict_mongodb->dict.type,
    291 		     dict_mongodb->dict.name, bson_iter_key(iter),
    292 		     bson_iter_type(iter));
    293 	    break;
    294 	}
    295     }
    296     if (dict_mongodb->dict.error != DICT_ERR_NONE || !got_one_result)
    297 	return (0);
    298     return (vstring_str(resultString));
    299 }
    300 
    301 /* dict_mongdb_quote - quote json string */
    302 
    303 static void dict_mongdb_quote(DICT *dict, const char *name, VSTRING *result)
    304 {
    305     /* quote_for_json_append() will resize the result buffer as needed. */
    306     (void) quote_for_json_append(result, name, -1);
    307 }
    308 
    309 /* dict_mongdb_append_result_attributes - projection builder */
    310 
    311 static int dict_mongdb_append_result_attribute(bson_t * projection,
    312 				               const char *result_attribute)
    313 {
    314     char   *ra = mystrdup(result_attribute);
    315     char   *pp = ra;
    316     char   *cp;
    317     int     ok = 1;
    318 
    319     while (ok && (cp = mystrtok(&pp, CHARS_COMMA_SP)) != 0)
    320 	ok = BSON_APPEND_INT32(projection, cp, 1);
    321     myfree(ra);
    322     return (ok);
    323 }
    324 
    325 /* dict_mongodb_lookup - find database entry using mongo query language */
    326 
    327 static const char *dict_mongodb_lookup(DICT *dict, const char *name)
    328 {
    329     DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
    330     mongoc_collection_t *coll = NULL;
    331     mongoc_cursor_t *cursor = NULL;
    332     bson_iter_t iter;
    333     const bson_t *doc = NULL;
    334     bson_t *query = NULL;
    335     bson_t *options = NULL;
    336     bson_t *projection = NULL;
    337     bson_error_t error;
    338     char   *result = NULL;
    339     static VSTRING *queryString = NULL;
    340     static VSTRING *resultString = NULL;
    341     int     domain_rc;
    342     int     expansion = 0;
    343 
    344     dict_mongodb->dict.error = DICT_ERR_NONE;
    345 
    346     /*
    347      * If they specified a domain list for this map, then only search for
    348      * addresses in domains on the list. This can significantly reduce the
    349      * load on the database.
    350      */
    351     if ((domain_rc = db_common_check_domain(dict_mongodb->ctx, name)) == 0) {
    352 	if (msg_verbose)
    353 	    msg_info("%s:%s: skipping lookup of '%s': domain mismatch",
    354 		     dict_mongodb->dict.type, dict_mongodb->dict.name, name);
    355 	return (0);
    356     } else if (domain_rc < 0) {
    357 	DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
    358     }
    359 
    360     /*
    361      * Ugly macros to make error and non-error handling code more readable.
    362      * If code size is a concern, them an optimizing compiler can eliminate
    363      * dead code or duplicated code.
    364      */
    365 
    366     /* Set an error code, and return null. */
    367 #define DICT_MONGODB_LOOKUP_ERR_RETURN(err) do { \
    368 	dict_mongodb->dict.error = (err); \
    369 	DICT_MONGODB_LOOKUP_RETURN((char *) 0); \
    370 } while (0);
    371 
    372     /* Pass through any error, and return the specified value. */
    373 #define DICT_MONGODB_LOOKUP_RETURN(val) do { \
    374 	if (coll) mongoc_collection_destroy(coll); \
    375 	if (cursor) mongoc_cursor_destroy(cursor); \
    376 	if (query) bson_destroy(query); \
    377 	if (options) bson_destroy(options); \
    378 	if (projection) bson_destroy(projection); \
    379 	return (val); \
    380     } while (0)
    381 
    382     coll = mongoc_client_get_collection(dict_mongodb->client,
    383 					dict_mongodb->dbname,
    384 					dict_mongodb->collection);
    385     if (!coll) {
    386 	msg_warn("%s:%s: failed to get collection [%s] from [%s]",
    387 		 dict_mongodb->dict.type, dict_mongodb->dict.name,
    388 		 dict_mongodb->collection, dict_mongodb->dbname);
    389 	DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    390     }
    391 
    392     /*
    393      * Use the specified result projection, or craft one from the
    394      * result_attribute. Exclude the _id field from the result.
    395      */
    396     options = bson_new();
    397     if (dict_mongodb->projection) {
    398 	projection = bson_new_from_json((uint8_t *) dict_mongodb->projection,
    399 					-1, &error);
    400 	if (!projection) {
    401 	    msg_warn("%s:%s: failed to create a projection from '%s': %s",
    402 		     dict_mongodb->dict.type, dict_mongodb->dict.name,
    403 		     dict_mongodb->projection, error.message);
    404 	    DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    405 	}
    406 	if (!BSON_APPEND_INT32(projection, "_id", 0)
    407 	    || !BSON_APPEND_DOCUMENT(options, "projection", projection)) {
    408 	    msg_warn("%s:%s: failed to append a projection from '%s'",
    409 		     dict_mongodb->dict.type, dict_mongodb->dict.name,
    410 		     dict_mongodb->projection);
    411 	    DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    412 	}
    413     } else if (dict_mongodb->result_attribute) {
    414 	bson_t  res_attr;
    415 
    416 	if (!BSON_APPEND_DOCUMENT_BEGIN(options, "projection", &res_attr)
    417 	    || !BSON_APPEND_INT32(&res_attr, "_id", 0)
    418 	    || !dict_mongdb_append_result_attribute(&res_attr,
    419 					     dict_mongodb->result_attribute)
    420 	    || !bson_append_document_end(options, &res_attr)) {
    421 	    msg_warn("%s:%s: failed to append a projection from '%s'",
    422 		     dict_mongodb->dict.type, dict_mongodb->dict.name,
    423 		     dict_mongodb->result_attribute);
    424 	    DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    425 	}
    426     } else {
    427 	/* Can't happen. The configuration parser should reject this. */
    428 	msg_panic("%s:%s: empty 'projection' and 'result_attribute'",
    429 		  dict_mongodb->dict.type, dict_mongodb->dict.name);
    430     }
    431 
    432     /*
    433      * Expand filter template. This uses a quoting function to prevent
    434      * metacharacter injection with parts from a crafted email address.
    435      */
    436     INIT_VSTR(queryString, BUFFER_SIZE);
    437     if (!db_common_expand(dict_mongodb->ctx, dict_mongodb->query_filter,
    438 			  name, 0, queryString, dict_mongdb_quote))
    439 	/* Suppress the actual lookup if the expansion is empty. */
    440 	DICT_MONGODB_LOOKUP_RETURN(0);
    441 
    442     /* Create the query from the expanded query template. */
    443     query = bson_new_from_json((uint8_t *) vstring_str(queryString),
    444 			       -1, &error);
    445     if (!query) {
    446 	msg_warn("%s:%s: failed to create a query from '%s': %s",
    447 		 dict_mongodb->dict.type, dict_mongodb->dict.name,
    448 		 vstring_str(queryString), error.message);
    449 	DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    450     }
    451     /* Run the query. */
    452     cursor = mongoc_collection_find_with_opts(coll, query, options, NULL);
    453     if (mongoc_cursor_error(cursor, &error)) {
    454 	msg_warn("%s:%s: cursor error for '%s': %s",
    455 		 dict_mongodb->dict.type, dict_mongodb->dict.name,
    456 		 vstring_str(queryString), error.message);
    457 	DICT_MONGODB_LOOKUP_ERR_RETURN(DICT_ERR_RETRY);
    458     }
    459     /* Convert the lookup result to C string. */
    460     INIT_VSTR(resultString, BUFFER_SIZE);
    461     while (mongoc_cursor_next(cursor, &doc)) {
    462 	if (bson_iter_init(&iter, doc)) {
    463 	    result = get_result_string(dict_mongodb, resultString, &iter,
    464 				       name, &expansion, name);
    465 	}
    466     }
    467     DICT_MONGODB_LOOKUP_RETURN(result);
    468 }
    469 
    470 /* dict_mongodb_close - close MongoDB database */
    471 
    472 static void dict_mongodb_close(DICT *dict)
    473 {
    474     DICT_MONGODB *dict_mongodb = (DICT_MONGODB *) dict;
    475 
    476     cfg_parser_free(dict_mongodb->parser);
    477     if (dict_mongodb->ctx) {
    478 	db_common_free_ctx(dict_mongodb->ctx);
    479     }
    480     myfree(dict_mongodb->uri);
    481     myfree(dict_mongodb->dbname);
    482     myfree(dict_mongodb->collection);
    483     myfree(dict_mongodb->query_filter);
    484 
    485     if (dict_mongodb->result_attribute) {
    486 	myfree(dict_mongodb->result_attribute);
    487     }
    488     if (dict_mongodb->result_format) {
    489 	myfree(dict_mongodb->result_format);
    490     }
    491     if (dict_mongodb->projection) {
    492 	myfree(dict_mongodb->projection);
    493     }
    494     if (dict_mongodb->client) {
    495 	mongoc_client_destroy(dict_mongodb->client);
    496     }
    497     dict_free(dict);
    498 }
    499 
    500 /* dict_mongodb_open - open MongoDB database connection */
    501 
    502 DICT   *dict_mongodb_open(const char *name, int open_flags, int dict_flags)
    503 {
    504     DICT_MONGODB *dict_mongodb;
    505     CFG_PARSER *parser;
    506     mongoc_uri_t *uri = 0;
    507     bson_error_t error;
    508 
    509     /* Sanity checks. */
    510     if (open_flags != O_RDONLY) {
    511 	return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
    512 			       "%s:%s: map requires O_RDONLY access mode",
    513 			       DICT_TYPE_MONGODB, name));
    514     }
    515     /* Open the configuration file. */
    516     if ((parser = cfg_parser_alloc(name)) == 0) {
    517 	return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
    518 			       "open %s: %m", name));
    519     }
    520     /* Create the dictionary object. */
    521     dict_mongodb = (DICT_MONGODB *) dict_alloc(DICT_TYPE_MONGODB, name,
    522 					       sizeof(*dict_mongodb));
    523     dict_mongodb->dict.lookup = dict_mongodb_lookup;
    524     dict_mongodb->dict.close = dict_mongodb_close;
    525     dict_mongodb->dict.flags = dict_flags;
    526     dict_mongodb->parser = parser;
    527     dict_mongodb->dict.owner = cfg_get_owner(dict_mongodb->parser);
    528     dict_mongodb->client = NULL;
    529 
    530     /* Parse config. */
    531     mongodb_parse_config(dict_mongodb, name);
    532     if (!dict_mongodb->projection == !dict_mongodb->result_attribute) {
    533 	dict_mongodb_close(&dict_mongodb->dict);
    534 	return (dict_surrogate(DICT_TYPE_MONGODB, name, open_flags, dict_flags,
    535 	 "%s:%s: specify exactly one of 'projection' or 'result_attribute'",
    536 			       DICT_TYPE_MONGODB, name));
    537     }
    538     /* One-time initialization of libmongoc 's internals. */
    539     if (!init_done) {
    540 	mongoc_init();
    541 	init_done = true;
    542     }
    543 #define DICT_MONGODB_OPEN_ERR_RETURN(d) do { \
    544 	DICT   *_d = (d); \
    545 	if (uri) mongoc_uri_destroy(uri); \
    546 	dict_mongodb_close(&dict_mongodb->dict); \
    547 	return (_d); \
    548     } while (0);
    549 
    550     uri = mongoc_uri_new_with_error(dict_mongodb->uri, &error);
    551     if (!uri)
    552 	DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
    553 						    open_flags, dict_flags,
    554 				      "%s:%s: failed to parse URI '%s': %s",
    555 						    DICT_TYPE_MONGODB, name,
    556 					 dict_mongodb->uri, error.message));
    557 
    558     dict_mongodb->client = mongoc_client_new_from_uri_with_error(uri, &error);
    559     if (!dict_mongodb->client)
    560 	DICT_MONGODB_OPEN_ERR_RETURN(dict_surrogate(DICT_TYPE_MONGODB, name,
    561 						    open_flags, dict_flags,
    562 			      "%s:%s: failed to create client for '%s': %s",
    563 						    DICT_TYPE_MONGODB, name,
    564 						    dict_mongodb->uri,
    565 						    error.message));
    566 
    567     mongoc_uri_destroy(uri);
    568     mongoc_client_set_error_api(dict_mongodb->client, MONGOC_ERROR_API_VERSION_2);
    569     return (&dict_mongodb->dict);
    570 }
    571 
    572 #endif
    573