const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key) { const char *myname = "dict_cache_lookup"; const char *cache_val; DICT *db = cp->db; /* * Search for the cache entry. Don't return an entry that is scheduled * for delete-behind. */ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s (pretend not found - scheduled for deletion)", myname, cache_key); DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0); } else { cache_val = dict_get(db, cache_key); if (cache_val == 0 && db->error != 0) msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn, "%s: cache lookup for '%s' failed due to error", cp->name, cache_key); if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s value=%s", myname, cache_key, cache_val ? cache_val : db->error ? "error" : "(not found)"); DICT_ERR_VAL_RETURN(cp, db->error, cache_val); } }
int dict_cache_delete(DICT_CACHE *cp, const char *cache_key) { const char *myname = "dict_cache_delete"; int del_res; DICT *db = cp->db; /* * Delete the entry, unless we would delete the current first/next entry. * In that case, schedule the "current" entry for delete-behind to avoid * mis-behavior by some databases. */ if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { DC_SCHEDULE_FOR_DELETE_BEHIND(cp); if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s (current entry - schedule for delete-behind)", myname, cache_key); DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS); } else { del_res = dict_del(db, cache_key); if (del_res != 0) msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, "%s: could not delete entry for %s", cp->name, cache_key); if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s (%s)", myname, cache_key, del_res == 0 ? "found" : db->error ? "error" : "not found"); DICT_ERR_VAL_RETURN(cp, db->error, del_res); } }
static const char *dict_random_lookup(DICT *dict, const char *unused_query) { DICT_RANDOM *dict_random = (DICT_RANDOM *) dict; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict_random->replies->argv[myrand() % dict_random->replies->argc]); }
static const char *dict_union_lookup(DICT *dict, const char *query) { static const char myname[] = "dict_union_lookup"; DICT_UNION *dict_union = (DICT_UNION *) dict; DICT *map; char **cpp; char *dict_type_name; const char *result = 0; /* * After Roel van Meer, postfix-users mailing list, Sept 2014. */ VSTRING_RESET(dict_union->re_buf); for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) { if ((map = dict_handle(dict_type_name)) == 0) msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name); if ((result = dict_get(map, query)) == 0) continue; if (VSTRING_LEN(dict_union->re_buf) > 0) VSTRING_ADDCH(dict_union->re_buf, ','); vstring_strcat(dict_union->re_buf, result); } DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, VSTRING_LEN(dict_union->re_buf) > 0 ? STR(dict_union->re_buf) : 0); }
static int dict_ht_sequence(DICT *dict, int how, const char **name, const char **value) { DICT_HT *dict_ht = (DICT_HT *) dict; HTABLE_INFO *ht; ht = htable_sequence(dict_ht->table, how == DICT_SEQ_FUN_FIRST ? HTABLE_SEQ_FIRST : how == DICT_SEQ_FUN_NEXT ? HTABLE_SEQ_NEXT : HTABLE_SEQ_STOP); if (ht != 0) { *name = ht->key; *value = ht->value; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); } else { *name = 0; *value = 0; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); } }
static const char *dict_ht_lookup(DICT *dict, const char *name) { DICT_HT *dict_ht = (DICT_HT *) dict; /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, htable_find(dict_ht->table, name)); }
static int dict_thash_sequence(DICT *dict, int function, const char **key, const char **value) { const char *myname = "dict_thash_sequence"; DICT_THASH *dict_thash = (DICT_THASH *) dict; /* * Determine and execute the seek function. */ switch (function) { case DICT_SEQ_FUN_FIRST: if (dict_thash->info == 0) dict_thash->info = htable_list(dict_thash->table); dict_thash->cursor = dict_thash->info; break; case DICT_SEQ_FUN_NEXT: if (dict_thash->cursor[0]) dict_thash->cursor += 1; break; default: msg_panic("%s: invalid function: %d", myname, function); } /* * Return the entry under the cursor. */ if (dict_thash->cursor[0]) { *key = dict_thash->cursor[0]->key; *value = dict_thash->cursor[0]->value; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); } else { *key = 0; *value = 0; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); } }
static const char *dict_thash_lookup(DICT *dict, const char *name) { DICT_THASH *dict_thash = (DICT_THASH *) dict; const char *result = 0; /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * Look up the value. */ result = htable_find(dict_thash->table, name); DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, result); }
static int dict_ht_update(DICT *dict, const char *name, const char *value) { DICT_HT *dict_ht = (DICT_HT *) dict; HTABLE_INFO *ht; char *saved_value = mystrdup(value); /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } if ((ht = htable_locate(dict_ht->table, name)) != 0) { myfree(ht->value); } else { ht = htable_enter(dict_ht->table, name, (char *) 0); } ht->value = saved_value; DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); }
int dict_cache_update(DICT_CACHE *cp, const char *cache_key, const char *cache_val) { const char *myname = "dict_cache_update"; DICT *db = cp->db; int put_res; /* * Store the cache entry and cancel the delete-behind operation. */ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: cancel delete-behind for key=%s", myname, cache_key); DC_CANCEL_DELETE_BEHIND(cp); } if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s value=%s", myname, cache_key, cache_val); put_res = dict_put(db, cache_key, cache_val); if (put_res != 0) msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn, "%s: could not update entry for %s", cp->name, cache_key); DICT_ERR_VAL_RETURN(cp, db->error, put_res); }
int dict_cache_sequence(DICT_CACHE *cp, int first_next, const char **cache_key, const char **cache_val) { const char *myname = "dict_cache_sequence"; int seq_res; const char *raw_cache_key; const char *raw_cache_val; char *previous_curr_key; char *previous_curr_val; DICT *db = cp->db; /* * Find the first or next database entry. Hide the record with the cache * cleanup completion time stamp. */ seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val); if (seq_res == 0 && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0) seq_res = dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val); if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: key=%s value=%s", myname, seq_res == 0 ? raw_cache_key : db->error ? "(error)" : "(not found)", seq_res == 0 ? raw_cache_val : db->error ? "(error)" : "(not found)"); if (db->error) msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn, "%s: sequence error", cp->name); /* * Save the current cache_key and cache_val before they are clobbered by * our own delete operation below. This also prevents surprises when the * application accesses the database after this function returns. * * We also use the saved cache_key to protect the current entry against * application delete requests. */ previous_curr_key = cp->saved_curr_key; previous_curr_val = cp->saved_curr_val; if (seq_res == 0) { cp->saved_curr_key = mystrdup(raw_cache_key); cp->saved_curr_val = mystrdup(raw_cache_val); } else { cp->saved_curr_key = 0; cp->saved_curr_val = 0; } /* * Delete behind. */ if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) { DC_CANCEL_DELETE_BEHIND(cp); if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) msg_info("%s: delete-behind key=%s value=%s", myname, previous_curr_key, previous_curr_val); if (dict_del(db, previous_curr_key) != 0) msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, "%s: could not delete entry for %s", cp->name, previous_curr_key); } /* * Clean up previous iteration key and value. */ if (previous_curr_key) myfree(previous_curr_key); if (previous_curr_val) myfree(previous_curr_val); /* * Return the result. */ *cache_key = (cp)->saved_curr_key; *cache_val = (cp)->saved_curr_val; DICT_ERR_VAL_RETURN(cp, db->error, seq_res); }
static const char *dict_sqlite_lookup(DICT *dict, const char *name) { const char *myname = "dict_sqlite_lookup"; DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict; sqlite3_stmt *sql_stmt; const char *query_remainder; static VSTRING *query; static VSTRING *result; const char *retval; int expansion = 0; int status; int domain_rc; /* * In case of return without lookup (skipped key, etc.). */ dict->error = 0; /* * Don't frustrate future attempts to make Postfix UTF-8 transparent. */ if (!valid_utf_8(name, strlen(name))) { if (msg_verbose) msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", myname, dict_sqlite->parser->name, name); return (0); } /* * Optionally fold the key. Folding may be enabled on on-the-fly. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(100); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * Apply the optional domain filter for email address lookups. */ if ((domain_rc = db_common_check_domain(dict_sqlite->ctx, name)) == 0) { if (msg_verbose) msg_info("%s: %s: Skipping lookup of '%s'", myname, dict_sqlite->parser->name, name); return (0); } if (domain_rc < 0) DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0); /* * Expand the query and query the database. */ #define INIT_VSTR(buf, len) do { \ if (buf == 0) \ buf = vstring_alloc(len); \ VSTRING_RESET(buf); \ VSTRING_TERMINATE(buf); \ } while (0) INIT_VSTR(query, 10); if (!db_common_expand(dict_sqlite->ctx, dict_sqlite->query, name, 0, query, dict_sqlite_quote)) return (0); if (msg_verbose) msg_info("%s: %s: Searching with query %s", myname, dict_sqlite->parser->name, vstring_str(query)); if (sqlite3_prepare_v2(dict_sqlite->db, vstring_str(query), -1, &sql_stmt, &query_remainder) != SQLITE_OK) msg_fatal("%s: %s: SQL prepare failed: %s\n", myname, dict_sqlite->parser->name, sqlite3_errmsg(dict_sqlite->db)); if (*query_remainder && msg_verbose) msg_info("%s: %s: Ignoring text at end of query: %s", myname, dict_sqlite->parser->name, query_remainder); /* * Retrieve and expand the result(s). */ INIT_VSTR(result, 10); while ((status = sqlite3_step(sql_stmt)) != SQLITE_DONE) { if (status == SQLITE_ROW) { if (db_common_expand(dict_sqlite->ctx, dict_sqlite->result_format, (char *) sqlite3_column_text(sql_stmt, 0), name, result, 0) && dict_sqlite->expansion_limit > 0 && ++expansion > dict_sqlite->expansion_limit) { msg_warn("%s: %s: Expansion limit exceeded for key '%s'", myname, dict_sqlite->parser->name, name); dict->error = DICT_ERR_RETRY; break; } } /* Fix 20100616 */ else { msg_warn("%s: %s: SQL step failed for query '%s': %s\n", myname, dict_sqlite->parser->name, vstring_str(query), sqlite3_errmsg(dict_sqlite->db)); dict->error = DICT_ERR_RETRY; break; } } /* * Clean up. */ if (sqlite3_finalize(sql_stmt)) msg_fatal("%s: %s: SQL finalize failed for query '%s': %s\n", myname, dict_sqlite->parser->name, vstring_str(query), sqlite3_errmsg(dict_sqlite->db)); return ((dict->error == 0 && *(retval = vstring_str(result)) != 0) ? retval : 0); }
static const char *dict_static_lookup(DICT *dict, const char *unused_name) { DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict->name); }
static const char *dict_mysql_lookup(DICT *dict, const char *name) { const char *myname = "dict_mysql_lookup"; DICT_MYSQL *dict_mysql = (DICT_MYSQL *) dict; MYSQL_RES *query_res; MYSQL_ROW row; static VSTRING *result; static VSTRING *query; int i; int j; int numrows; int expansion; const char *r; db_quote_callback_t quote_func = dict_mysql_quote; int domain_rc; dict->error = 0; /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * If there is a domain list for this map, then only search for addresses * in domains on the list. This can significantly reduce the load on the * server. */ if ((domain_rc = db_common_check_domain(dict_mysql->ctx, name)) == 0) { if (msg_verbose) msg_info("%s: Skipping lookup of '%s'", myname, name); return (0); } if (domain_rc < 0) DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0); #define INIT_VSTR(buf, len) do { \ if (buf == 0) \ buf = vstring_alloc(len); \ VSTRING_RESET(buf); \ VSTRING_TERMINATE(buf); \ } while (0) INIT_VSTR(query, 10); /* * Suppress the lookup if the query expansion is empty * * This initial expansion is outside the context of any specific host * connection, we just want to check the key pre-requisites, so when * quoting happens separately for each connection, we don't bother with * quoting... */ #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 quote_func = 0; #endif if (!db_common_expand(dict_mysql->ctx, dict_mysql->query, name, 0, query, quote_func)) return (0); /* do the query - set dict->error & cleanup if there's an error */ if ((query_res = plmysql_query(dict_mysql, name, query)) == 0) { dict->error = DICT_ERR_RETRY; return (0); } numrows = mysql_num_rows(query_res); if (msg_verbose) msg_info("%s: retrieved %d rows", myname, numrows); if (numrows == 0) { mysql_free_result(query_res); return 0; } INIT_VSTR(result, 10); for (expansion = i = 0; i < numrows && dict->error == 0; i++) { row = mysql_fetch_row(query_res); for (j = 0; j < mysql_num_fields(query_res); j++) { if (db_common_expand(dict_mysql->ctx, dict_mysql->result_format, row[j], name, result, 0) && dict_mysql->expansion_limit > 0 && ++expansion > dict_mysql->expansion_limit) { msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", myname, dict_mysql->parser->name, name); dict->error = DICT_ERR_RETRY; break; } } } mysql_free_result(query_res); r = vstring_str(result); return ((dict->error == 0 && *r) ? r : 0); }
static const char *dict_pgsql_lookup(DICT *dict, const char *name) { const char *myname = "dict_pgsql_lookup"; PGSQL_RES *query_res; DICT_PGSQL *dict_pgsql; PLPGSQL *pldb; static VSTRING *query; static VSTRING *result; int i; int j; int numrows; int numcols; int expansion; const char *r; int domain_rc; dict_pgsql = (DICT_PGSQL *) dict; pldb = dict_pgsql->pldb; #define INIT_VSTR(buf, len) do { \ if (buf == 0) \ buf = vstring_alloc(len); \ VSTRING_RESET(buf); \ VSTRING_TERMINATE(buf); \ } while (0) INIT_VSTR(query, 10); INIT_VSTR(result, 10); dict->error = 0; /* * Optionally fold the key. */ if (dict->flags & DICT_FLAG_FOLD_FIX) { if (dict->fold_buf == 0) dict->fold_buf = vstring_alloc(10); vstring_strcpy(dict->fold_buf, name); name = lowercase(vstring_str(dict->fold_buf)); } /* * If there is a domain list for this map, then only search for addresses * in domains on the list. This can significantly reduce the load on the * server. */ if ((domain_rc = db_common_check_domain(dict_pgsql->ctx, name)) == 0) { if (msg_verbose) msg_info("%s: Skipping lookup of '%s'", myname, name); return (0); } if (domain_rc < 0) DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0); /* * Suppress the actual lookup if the expansion is empty. * * This initial expansion is outside the context of any specific host * connection, we just want to check the key pre-requisites, so when * quoting happens separately for each connection, we don't bother with * quoting... */ if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query, name, 0, query, 0)) return (0); /* do the query - set dict->error & cleanup if there's an error */ if ((query_res = plpgsql_query(dict_pgsql, name, query, dict_pgsql->dbname, dict_pgsql->username, dict_pgsql->password)) == 0) { dict->error = DICT_ERR_RETRY; return 0; } numrows = PQntuples(query_res); if (msg_verbose) msg_info("%s: retrieved %d rows", myname, numrows); if (numrows == 0) { PQclear(query_res); return 0; } numcols = PQnfields(query_res); for (expansion = i = 0; i < numrows && dict->error == 0; i++) { for (j = 0; j < numcols; j++) { r = PQgetvalue(query_res, i, j); if (db_common_expand(dict_pgsql->ctx, dict_pgsql->result_format, r, name, result, 0) && dict_pgsql->expansion_limit > 0 && ++expansion > dict_pgsql->expansion_limit) { msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", myname, dict_pgsql->parser->name, name); dict->error = DICT_ERR_RETRY; break; } } } PQclear(query_res); r = vstring_str(result); return ((dict->error == 0 && *r) ? r : 0); }