Hi list,
I'm in the process of adding write support to postfix's mysql client (you will
find a patch against postfix-2.7.1 in the appendix). But I have two problems:
1) the dict_cache_clean_event writes _LAST_CACHE_CLEANUP_COMPLETED_ to the
database. Is this the intended behaviour?
2) If I'm guessing right then the dict_cache_clean_event will iterate with
help of dict->sequence through the database and will look for keys to expire.
But I don't know how to implement this iteration/traverse process with mysql.
My first thought was to use "SELECT * FROM verify" and mysql_use_result() but
I'm wondering if there is a better solution.
Has anyone an idea of how to do this?
Thanks for your help and best regards
Stefan
> > by Stefan Jakobs on 2010-06-13T19:43:00+00:00
> > Hello list,
> > I refer to my question of august 2008
> > (http://archives.neohapsis.com/archives/postfix/2008-08/0747.html, and see
> > below).
> > What are the necessary steps to add update support to the mysql client
> > (Postfix 2.5.6 or newer)?
> > Has someone already done this and is willing to share the code?
> > Thanks for your help and kind regards
> > Stefan
> Wietse wrote on August 22nd 2008:
> Stefan Jakobs:
> I think this involves writing, testing, and documenting code. The
> design stage can pretty much be skipped for this fill-in-the-blanks
> exercise.
> Wietse
diff -ur postfix-2.7.1.orig/src/global/db_common.c postfix-2.7.1/src/global/db_common.c
--- postfix-2.7.1.orig/src/global/db_common.c 2009-10-05 22:33:16.000000000 +0200
+++ postfix-2.7.1/src/global/db_common.c 2010-09-24 13:51:27.000000000 +0200
@@ -283,7 +283,7 @@
/* db_common_expand - expand query and result templates */
int db_common_expand(void *ctxArg, const char *format, const char *value,
- const char *key, VSTRING *result,
+ const char *data, const char *key, VSTRING *result,
db_quote_callback_t quote_func)
{
const char *myname = "db_common_expand";
@@ -382,7 +382,8 @@
} while (0)
/*
- * Replace all instances of %s with the address to look up. Replace %u
+ * Replace all instances of %s with the address to look up. Replace %v
+ * with the data value portion. Replace %u
* with the user portion, and %d with the domain portion. "%%" expands to
* "%". lowercase -> addr, uppercase -> key
*/
@@ -398,6 +399,16 @@
QUOTE_VAL(ctx->dict, quote_func, value, result);
break;
+ case 'v':
+ /* Don't silenty skip empty query string */
+ if (*data == 0) {
+ msg_warn("table \"%s:%s\": empty query string"
+ " -- ignored", ctx->dict->type, ctx->dict->name);
+ return (0);
+ }
+ QUOTE_VAL(ctx->dict, quote_func, data, result);
+ break;
+
case 'u':
if (vdomain) {
if (vuser == 0)
@@ -424,6 +435,18 @@
QUOTE_VAL(ctx->dict, quote_func, value, result);
break;
+ case 'V':
+ if (! key) {
+ /* Don't silenty skip empty query string */
+ if (*data == 0) {
+ msg_warn("table \"%s:%s\": empty query string"
+ " -- ignored", ctx->dict->type, ctx->dict->name);
+ return (0);
+ }
+ QUOTE_VAL(ctx->dict, quote_func, data, result);
+ }
+ break;
+
case 'U':
if (key) {
if (kdomain) {
diff -ur postfix-2.7.1.orig/src/global/db_common.h postfix-2.7.1/src/global/db_common.h
--- postfix-2.7.1.orig/src/global/db_common.h 2005-09-23 01:50:50.000000000 +0200
+++ postfix-2.7.1/src/global/db_common.h 2010-09-24 13:51:27.000000000 +0200
@@ -23,7 +23,7 @@
extern int db_common_parse(DICT *, void **, const char *, int);
extern void db_common_parse_domain(CFG_PARSER *, void *);
extern int db_common_dict_partial(void *);
-extern int db_common_expand(void *, const char *, const char *,
+extern int db_common_expand(void *, const char *, const char *, const char *,
const char *, VSTRING *, db_quote_callback_t);
extern int db_common_check_domain(void *, const char *);
extern void db_common_free_ctx(void *);
diff -ur postfix-2.7.1.orig/src/global/dict_ldap.c postfix-2.7.1/src/global/dict_ldap.c
--- postfix-2.7.1.orig/src/global/dict_ldap.c 2010-05-30 00:08:26.000000000 +0200
+++ postfix-2.7.1/src/global/dict_ldap.c 2010-09-24 13:51:27.000000000 +0200
@@ -978,7 +978,7 @@
if (db_common_expand(dict_ldap->ctx,
dict_ldap->result_format,
vals[i]->bv_val,
- name, result, 0)
+ NULL, name, result, 0)
&& dict_ldap->expansion_limit > 0
&& ++expansion > dict_ldap->expansion_limit) {
msg_warn("%s[%d]: %s: Expansion limit exceeded "
@@ -1182,14 +1182,14 @@
* rather than a soft error.
*/
if (!db_common_expand(dict_ldap->ctx, dict_ldap->search_base,
- name, 0, base, rfc2253_quote)) {
+ name, NULL, 0, base, rfc2253_quote)) {
if (msg_verbose > 1)
msg_info("%s: %s: Empty expansion for %s", myname,
dict_ldap->parser->name, dict_ldap->search_base);
return (0);
}
if (!db_common_expand(dict_ldap->ctx, dict_ldap->query,
- name, 0, query, rfc2254_quote)) {
+ name, NULL, 0, query, rfc2254_quote)) {
if (msg_verbose > 1)
msg_info("%s: %s: Empty expansion for %s", myname,
dict_ldap->parser->name, dict_ldap->query);
diff -ur postfix-2.7.1.orig/src/global/dict_mysql.c postfix-2.7.1/src/global/dict_mysql.c
--- postfix-2.7.1.orig/src/global/dict_mysql.c 2007-01-04 21:07:38.000000000 +0100
+++ postfix-2.7.1/src/global/dict_mysql.c 2010-10-01 18:00:31.000000000 +0200
@@ -42,7 +42,7 @@
/* .IP other_name
/* reference for outside use.
/* .IP open_flags
-/* Must be O_RDONLY.
+/* See open(2). Must be O_RDWR for write access.
/* .IP dict_flags
/* See dict_open(3).
/* .PP
@@ -69,6 +69,18 @@
/* No query is specified, the legacy variables \fItable\fR,
/* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR
/* are used to construct the query template.
+/* .IP \fIinsert\fR
+/* Insert template, before the query is actually issued, variable
+/* substitutions are performed. See mysql_table(5) for details.
+/* Legacy variables are not available for this query.
+/* .IP \fIupdate\fR
+/* Update template, before the query is actually issued, variable
+/* substitutions are performed. See mysql_table(5) for details.
+/* Legacy variables are not available for this query.
+/* .IP \fIdelete\fR
+/* Delete template, before the query is actually issued, variable
+/* substitutions are performed. See mysql_table(5) for details.
+/* Legacy variables are not available for this query.
/* .IP \fIresult_format\fR
/* The format used to expand results from queries. Substitutions
/* are performed as described in mysql_table(5). Defaults to returning
@@ -220,6 +232,9 @@
DICT dict;
CFG_PARSER *parser;
char *query;
+ char *insert;
+ char *update;
+ char *delete;
char *result_format;
void *ctx;
int expansion_limit;
@@ -248,6 +263,8 @@
static PLMYSQL *plmysql_init(ARGV *);
static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *,
char *, char *);
+static int plmysql_update(DICT_MYSQL *, const char *, const char *,
+ const char *,VSTRING *, char *, char *, char *);
static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
static void plmysql_down_host(HOST *);
@@ -347,7 +364,7 @@
quote_func = 0;
#endif
if (!db_common_expand(dict_mysql->ctx, dict_mysql->query,
- name, 0, query, quote_func))
+ name, NULL, 0, query, quote_func))
return (0);
/* do the query - set dict_errno & cleanup if there's an error */
@@ -373,7 +390,7 @@
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)
+ row[j], NULL, name, result, 0)
&& dict_mysql->expansion_limit > 0
&& ++expansion > dict_mysql->expansion_limit) {
msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
@@ -388,6 +405,190 @@
return ((dict_errno == 0 && *r) ? r : 0);
}
+/* dict_mysql_update - update/insert database entry */
+
+static void dict_mysql_update(DICT *dict, const char *name, const char *val)
+{
+ const char *myname = "dict_mysql_update";
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+ MYSQL_RES *query_res;
+ static VSTRING *query;
+ int numrows;
+ db_quote_callback_t quote_func = dict_mysql_quote;
+
+ dict_errno = 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));
+ }
+
+#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, NULL, 0, query, quote_func))
+ return;
+
+ /* check if name exists already */
+ /* do the query - set dict_errno & cleanup if there's an error */
+ if ((query_res = plmysql_query(dict_mysql, name, query,
+ dict_mysql->dbname,
+ dict_mysql->username,
+ dict_mysql->password)) == 0) {
+ dict_errno = DICT_ERR_RETRY;
+ return;
+ }
+
+ numrows = mysql_num_rows(query_res);
+ mysql_free_result(query_res);
+ if (msg_verbose)
+ msg_info("%s: retrieved %d rows", myname, numrows);
+ if (numrows == 0) { /* do insert */
+ if (!db_common_expand(dict_mysql->ctx, dict_mysql->insert,
+ name, val, 0, query, quote_func))
+ return;
+
+ if ((numrows = plmysql_update(dict_mysql, name, val, dict_mysql->insert, query,
+ dict_mysql->dbname,
+ dict_mysql->username,
+ dict_mysql->password)) == 0) {
+ dict_errno = DICT_ERR_RETRY;
+ return;
+ }
+
+ } else { /* do update */
+ if (!db_common_expand(dict_mysql->ctx, dict_mysql->update,
+ name, val, 0, query, quote_func))
+ return;
+
+ if ((numrows = plmysql_update(dict_mysql, name, val, dict_mysql->update, query,
+ dict_mysql->dbname,
+ dict_mysql->username,
+ dict_mysql->password)) == 0) {
+ dict_errno = DICT_ERR_RETRY;
+ return;
+ }
+
+ }
+ if (msg_verbose)
+ msg_info("%s: updated %d rows", myname, numrows);
+}
+
+/* dict_mysql_delete - delete database entry */
+
+static int dict_mysql_delete(DICT *dict, const char *name)
+{
+ const char *myname = "dict_mysql_delete";
+ DICT_MYSQL *dict_mysql = (DICT_MYSQL *)dict;
+ static VSTRING *query;
+ int numrows;
+ db_quote_callback_t quote_func = dict_mysql_quote;
+
+ dict_errno = 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));
+ }
+
+#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->delete,
+ name, NULL, 0, query, quote_func))
+ return (0);
+
+ /* do the delete - set dict_errno & cleanup if there's an error */
+ if ((numrows = plmysql_update(dict_mysql, name, NULL, dict_mysql->delete, query,
+ dict_mysql->dbname,
+ dict_mysql->username,
+ dict_mysql->password)) == 0) {
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ if (msg_verbose)
+ msg_info("%s: deleted %d rows", myname, numrows);
+ if (numrows == 0) { /* failure */
+ msg_warn("%s: %s: delete failed, key: '%s'",
+ myname, dict_mysql->parser->name, name);
+ } /* OK */
+ return(numrows);
+}
+
+/* dict_mysql_sequence - ??? */
+
+static int dict_mysql_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_mysql_sequence";
+ int status;
+
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ *value = dict_mysql_lookup(dict, *key);
+ msg_warn("%s: cal DICT_SEQ_FUN_FIRST", myname);
+ status = 1;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ *value = NULL;
+ *key = NULL;
+ msg_warn("%s: cal DICT_SEQ_FUN_NEXT", myname);
+ status = 0;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ return(status);
+
+}
+
/* dict_mysql_check_stat - check the status of a host */
static int dict_mysql_check_stat(HOST *host, unsigned stat, unsigned type,
@@ -509,7 +710,7 @@
VSTRING_RESET(query);
VSTRING_TERMINATE(query);
db_common_expand(dict_mysql->ctx, dict_mysql->query,
- name, 0, query, dict_mysql_quote);
+ name, NULL, 0, query, dict_mysql_quote);
dict_mysql->active_host = 0;
#endif
@@ -533,6 +734,61 @@
}
/*
+ * plmysql_update - process a MySQL update, insert or delete query.
+ * Return number of affected rows on success.
+ * On failure, log failure and try other db instances.
+ * on failure of all db instances, return 0;
+ * close unnecessary active connections
+ */
+
+static int plmysql_update(DICT_MYSQL *dict_mysql,
+ const char *name,
+ const char *val,
+ const char *update,
+ VSTRING *query,
+ char *dbname,
+ char *username,
+ char *password)
+{
+ PLMYSQL *PLDB = dict_mysql->pldb;
+ HOST *host;
+ int numrows;
+
+ while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
+
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ /*
+ * The active host is used to escape strings in the
+ * context of the active connection's character encoding.
+ */
+ dict_mysql->active_host = host;
+ VSTRING_RESET(query);
+ VSTRING_TERMINATE(query);
+ db_common_expand(dict_mysql->ctx, update,
+ name, val, 0, query, dict_mysql_quote);
+ dict_mysql->active_host = 0;
+#endif
+
+ if (!(mysql_query(host->db, vstring_str(query)))) {
+ if ((numrows = mysql_affected_rows(host->db)) == 0) {
+ msg_warn("mysql update failed: %s", mysql_error(host->db));
+ plmysql_down_host(host);
+ } else {
+ if (msg_verbose)
+ msg_info("dict_mysql: successful update from host %s", host->hostname);
+ event_request_timer(dict_mysql_event, (char *) host, IDLE_CONN_INTV);
+ break;
+ }
+ } else {
+ msg_warn("mysql update failed: %s", mysql_error(host->db));
+ plmysql_down_host(host);
+ numrows = 0;
+ }
+ }
+ return (numrows);
+}
+
+/*
* plmysql_connect_single -
* used to reconnect to a single database when one is down or none is
* connected yet. Log all errors and set the stat field of host accordingly
@@ -613,6 +869,10 @@
dict_mysql->query = vstring_export(buf);
}
+ dict_mysql->insert = cfg_get_str(p, "insert", NULL, 0, 0);
+ dict_mysql->update = cfg_get_str(p, "update", NULL, 0, 0);
+ dict_mysql->delete = cfg_get_str(p, "delete", NULL, 0, 0);
+
/*
* Must parse all templates before we can use db_common_expand()
*/
@@ -654,14 +914,17 @@
/*
* Sanity checks.
- */
+ *
if (open_flags != O_RDONLY)
msg_fatal("%s:%s map requires O_RDONLY access mode",
DICT_TYPE_MYSQL, name);
-
+ */
dict_mysql = (DICT_MYSQL *) dict_alloc(DICT_TYPE_MYSQL, name,
sizeof(DICT_MYSQL));
dict_mysql->dict.lookup = dict_mysql_lookup;
+ dict_mysql->dict.update = dict_mysql_update;
+ dict_mysql->dict.delete = dict_mysql_delete;
+ dict_mysql->dict.sequence = dict_mysql_sequence;
dict_mysql->dict.close = dict_mysql_close;
dict_mysql->dict.flags = dict_flags;
mysql_parse_config(dict_mysql, name);
diff -ur postfix-2.7.1.orig/src/global/dict_pgsql.c postfix-2.7.1/src/global/dict_pgsql.c
--- postfix-2.7.1.orig/src/global/dict_pgsql.c 2007-01-04 21:07:59.000000000 +0100
+++ postfix-2.7.1/src/global/dict_pgsql.c 2010-09-24 13:51:27.000000000 +0200
@@ -378,7 +378,7 @@
* for each connection, we don't bother with quoting...
*/
if (!db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
- name, 0, query, 0))
+ name, NULL, 0, query, 0))
return (0);
/* do the query - set dict_errno & cleanup if there's an error */
@@ -403,7 +403,7 @@
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)
+ r, NULL, name, result, 0)
&& dict_pgsql->expansion_limit > 0
&& ++expansion > dict_pgsql->expansion_limit) {
msg_warn("%s: %s: Expansion limit exceeded for key: '%s'",
@@ -538,7 +538,7 @@
VSTRING_RESET(query);
VSTRING_TERMINATE(query);
db_common_expand(dict_pgsql->ctx, dict_pgsql->query,
- name, 0, query, dict_pgsql_quote);
+ name, NULL, 0, query, dict_pgsql_quote);
dict_pgsql->active_host = 0;
/* Check for potential dict_pgsql_quote() failure. */