2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-23 10:28:06 +00:00

postfix-3.2-20161226-nonprod

This commit is contained in:
Wietse Venema 2016-12-26 00:00:00 -05:00 committed by Viktor Dukhovni
parent 551bd26bfa
commit 5b0b13c24e
8 changed files with 233 additions and 192 deletions

1
postfix/.indent.pro vendored
View File

@ -223,6 +223,7 @@
-TMVECT
-TMYSQL
-TMYSQL_NAME
-TMYSQL_RES
-TNAMADR_LIST
-TNAME_ASSIGNMENT
-TNAME_CODE

View File

@ -22702,3 +22702,11 @@ Apologies for any names omitted.
Cleanup: simplified code structure in the MySQL client
support for stored procedures. File: global/dict_mysql.c.
20161226
Cleanup: more MySQL client code simplification, better error
messages, new per-database "require_result_set" parameter
(default: yes) which can be set to "no" to avoid the need
for dummy SELECT statements in stored procedures. Files:
global/dict_mysql.c, proto/mysql_table, postconf/postconf_dbms.c.

View File

@ -101,6 +101,11 @@ MYSQL_TABLE(5) MYSQL_TABLE(5)
a substitute for the address Postfix is trying to resolve, e.g.
query = SELECT replacement FROM aliases WHERE mailbox = '%s'
By default, every query must return a result set (instead of
storing its results in a table); with "<b>require_result_set = no</b>"
(Postfix 3.2 and later), the absence of a result set is treated
as "not found".
This parameter supports the following '%' expansions:
<b>%%</b> This is replaced by a literal '%' character.
@ -246,6 +251,12 @@ MYSQL_TABLE(5) MYSQL_TABLE(5)
This parameter is available with Postfix 2.11 and later.
<b>require_result_set (default: yes)</b>
If "<b>yes</b>", require that every query returns a result set. If
"<b>no</b>", treat the absence of a result set as "not found".
This parameter is available with Postfix 3.2 and later.
<b>tls_cert_file</b>
File containing client's X509 certificate.
@ -284,25 +295,18 @@ MYSQL_TABLE(5) MYSQL_TABLE(5)
The previously described '%' expansions can be used in the parameter(s)
to the stored procedure.
The stored procedure must return data with a single result set, that is
the stored procedure must execute exactly one SELECT statement on every
code path. If you have complex logic and for some paths you want to
return no result you will need to include a SELECT statement that
returns no rows. One example of a query that returns no rows is
By default, every stored procedure call must return a result set, i.e.
every code path must execute a SELECT statement that returns a result
set (instead of storing its results in a table). With
"<b>require_result_set = no</b>", the absence of a result set is treated as
"not found".
SELECT 1 FROM DUAL WHERE FALSE
but you may use your own query.
Stored procedures that return multiple result sets containing data,
that is stored procedures that execute multiple SELECT statements, are
not supported. Stored procedures in mysql produce an additional result
set without data which indicates the final status of the stored proce-
dure. If this final status is an error then any previous returned data
will not be used.
A stored procedure must not return multiple result sets. That is,
there must be no code path that executes multiple SELECT statements
that return a result (instead of storing their results in a table).
The following is an example of a stored procedure returning a single
result set containing data:
result set:
CREATE [DEFINER=`user`@`host`] PROCEDURE
`lookup`(IN `param` VARCHAR(255))
@ -376,5 +380,10 @@ MYSQL_TABLE(5) MYSQL_TABLE(5)
Stored-procedure support by John Fawcett.
Wietse Venema
Google, Inc.
111 8th Avenue
New York, NY 10011, USA
MYSQL_TABLE(5)
</pre> </body> </html>

View File

@ -132,6 +132,11 @@ e.g.
query = SELECT replacement FROM aliases WHERE mailbox = '%s'
.fi
By default, every query must return a result set (instead
of storing its results in a table); with "\fBrequire_result_set
= no\fR" (Postfix 3.2 and later), the absence of a result
set is treated as "not found".
This parameter supports the following '%' expansions:
.RS
.IP "\fB%%\fR"
@ -277,6 +282,12 @@ group settings unless a non\-empty \fBoption_file\fR or
specify, for example, "\fBoption_group = client\fR".
.sp
This parameter is available with Postfix 2.11 and later.
.IP "\fBrequire_result_set (default: yes)\fR"
If "\fByes\fR", require that every query returns a result
set. If "\fBno\fR", treat the absence of a result set as
"not found".
.sp
This parameter is available with Postfix 3.2 and later.
.IP "\fBtls_cert_file\fR"
File containing client's X509 certificate.
.sp
@ -316,29 +327,19 @@ instead of using a SELECT statement in the query, e.g.
The previously described '%' expansions can be used in the
parameter(s) to the stored procedure.
The stored procedure must return data with a single result
set, that is the stored procedure must execute exactly one
SELECT statement on every code path. If you have complex
logic and for some paths you want to return no result you
will need to include a SELECT statement that returns no
rows. One example of a query that returns no rows is
By default, every stored procedure call must return a result
set, i.e. every code path must execute a SELECT statement
that returns a result set (instead of storing its results
in a table). With "\fBrequire_result_set = no\fR", the
absence of a result set is treated as "not found".
.nf
SELECT 1 FROM DUAL WHERE FALSE
.fi
but you may use your own query.
Stored procedures that return multiple result sets containing
data, that is stored procedures that execute multiple SELECT
statements, are not supported. Stored procedures in mysql
produce an additional result set without data which indicates
the final status of the stored procedure. If this final
status is an error then any previous returned data will not
be used.
A stored procedure must not return multiple result sets.
That is, there must be no code path that executes multiple
SELECT statements that return a result (instead of storing
their results in a table).
The following is an example of a stored procedure returning
a single result set containing data:
a single result set:
.nf
CREATE [DEFINER=`user`@`host`] PROCEDURE
@ -437,3 +438,8 @@ P.O. BOX 1\-764
RO\-014700 Bucharest, ROMANIA
Stored\-procedure support by John Fawcett.
Wietse Venema
Google, Inc.
111 8th Avenue
New York, NY 10011, USA

View File

@ -120,6 +120,11 @@
# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
# .fi
#
# By default, every query must return a result set (instead
# of storing its results in a table); with "\fBrequire_result_set
# = no\fR" (Postfix 3.2 and later), the absence of a result
# set is treated as "not found".
#
# This parameter supports the following '%' expansions:
# .RS
# .IP "\fB%%\fR"
@ -265,6 +270,12 @@
# specify, for example, "\fBoption_group = client\fR".
# .sp
# This parameter is available with Postfix 2.11 and later.
# .IP "\fBrequire_result_set (default: yes)\fR"
# If "\fByes\fR", require that every query returns a result
# set. If "\fBno\fR", treat the absence of a result set as
# "not found".
# .sp
# This parameter is available with Postfix 3.2 and later.
# .IP "\fBtls_cert_file\fR"
# File containing client's X509 certificate.
# .sp
@ -302,29 +313,19 @@
# The previously described '%' expansions can be used in the
# parameter(s) to the stored procedure.
#
# The stored procedure must return data with a single result
# set, that is the stored procedure must execute exactly one
# SELECT statement on every code path. If you have complex
# logic and for some paths you want to return no result you
# will need to include a SELECT statement that returns no
# rows. One example of a query that returns no rows is
# By default, every stored procedure call must return a result
# set, i.e. every code path must execute a SELECT statement
# that returns a result set (instead of storing its results
# in a table). With "\fBrequire_result_set = no\fR", the
# absence of a result set is treated as "not found".
#
# .nf
# SELECT 1 FROM DUAL WHERE FALSE
# .fi
#
# but you may use your own query.
#
# Stored procedures that return multiple result sets containing
# data, that is stored procedures that execute multiple SELECT
# statements, are not supported. Stored procedures in mysql
# produce an additional result set without data which indicates
# the final status of the stored procedure. If this final
# status is an error then any previous returned data will not
# be used.
# A stored procedure must not return multiple result sets.
# That is, there must be no code path that executes multiple
# SELECT statements that return a result (instead of storing
# their results in a table).
#
# The following is an example of a stored procedure returning
# a single result set containing data:
# a single result set:
#
# .nf
# CREATE [DEFINER=`user`@`host`] PROCEDURE
@ -411,4 +412,9 @@
# RO-014700 Bucharest, ROMANIA
#
# Stored-procedure support by John Fawcett.
#
# Wietse Venema
# Google, Inc.
# 111 8th Avenue
# New York, NY 10011, USA
#--

View File

@ -96,6 +96,8 @@
/* location.
/* .IP option_group
/* Read options from the given group.
/* .IP require_result_set
/* Require that every query produces a result set.
/* .IP tls_cert_file
/* File containing client's X509 certificate.
/* .IP tls_key_file
@ -132,44 +134,25 @@
/* where_field = alias
/* .br
/* hosts = host1.some.domain host2.some.domain
/* .IP additional_conditions
/* Backward compatibility when \fIquery\fR is not set, additional
/* conditions to the WHERE clause.
/* .IP hosts
/* List of hosts to connect to.
/* .PP
/* For example, if you want the map to reference databases of
/* the name "your_db" and execute a query like this: select
/* forw_addr from aliases where alias like '<some username>'
/* against any database called "vmailer_info" located on hosts
/* host1.some.domain and host2.some.domain, logging in as user
/* "vmailer" and password "passwd" then the configuration file
/* should read:
/* .PP
/* user = vmailer
/* .br
/* password = passwd
/* .br
/* dbname = vmailer_info
/* .br
/* table = aliases
/* .br
/* select_field = forw_addr
/* .br
/* where_field = alias
/* .br
/* hosts = host1.some.domain host2.some.domain
/* .PP
/* SEE ALSO
/* dict(3) generic dictionary manager
/* AUTHOR(S)
/* Scott Cotton
/* Scott Cotton, Joshua Marcus
/* IC Group, Inc.
/* scott@icgroup.com
/*
/* Joshua Marcus
/* IC Group, Inc.
/* josh@icgroup.com
/* Liviu Daia
/* Institute of Mathematics of the Romanian Academy
/* P.O. BOX 1-764
/* RO-014700 Bucharest, ROMANIA
/*
/* John Fawcett
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
@ -258,6 +241,7 @@ typedef struct {
int tls_verify_cert;
#endif
#endif
int require_result_set;
} DICT_MYSQL;
#define STATACTIVE (1<<0)
@ -273,7 +257,7 @@ typedef struct {
/* internal function declarations */
static PLMYSQL *plmysql_init(ARGV *);
static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *);
static int plmysql_query(DICT_MYSQL *, const char *, VSTRING *, MYSQL_RES **);
static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
static void plmysql_down_host(HOST *);
@ -383,10 +367,12 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name)
return (0);
/* do the query - set dict->error & cleanup if there's an error */
if ((query_res = plmysql_query(dict_mysql, name, query)) == 0) {
if (plmysql_query(dict_mysql, name, query, &query_res) == 0) {
dict->error = DICT_ERR_RETRY;
return (0);
}
if (query_res == 0)
return (0);
numrows = mysql_num_rows(query_res);
if (msg_verbose)
msg_info("%s: retrieved %d rows", myname, numrows);
@ -508,18 +494,20 @@ static void dict_mysql_event(int unused_event, void *context)
}
/*
* plmysql_query - process a MySQL query. Return MYSQL_RES* on success.
* plmysql_query - process a MySQL query. Return 'true' on success.
* On failure, log failure and try other db instances.
* on failure of all db instances, return 0;
* on failure of all db instances, return 'false';
* close unnecessary active connections
*/
static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
static int plmysql_query(DICT_MYSQL *dict_mysql,
const char *name,
VSTRING *query)
VSTRING *query,
MYSQL_RES **result)
{
HOST *host;
MYSQL_RES *res = 0;
MYSQL_RES *first_result = 0;
int query_error;
/*
* Helper to avoid spamming the log with warnings.
@ -533,7 +521,6 @@ static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
} while (0)
while ((host = dict_mysql_get_active(dict_mysql)) != NULL) {
int query_error = 0;
#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
@ -549,56 +536,77 @@ static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
dict_mysql->active_host = 0;
#endif
query_error = 0;
errno = 0;
/*
* The query must complete.
*/
if (mysql_query(host->db, vstring_str(query)) != 0) {
query_error = 1;
msg_warn("mysql query failed: %s", mysql_error(host->db));
msg_warn("%s:%s: query failed: %s",
dict_mysql->dict.type, dict_mysql->dict.name,
mysql_error(host->db));
}
/*
* The query must return at least one result.
*/
else if ((res = mysql_store_result(host->db)) == 0) {
query_error = 1;
msg_warn("mysql query failed: no result set containing data");
}
/*
* Are there more results? -1 = no, 0 = yes, > 0 = error. We must
* collect all results to avoid synchronization errors.
* Collect all result sets to avoid synchronization errors.
*/
else {
int next_res_status;
MYSQL_RES *temp_res;
while ((next_res_status = mysql_next_result(host->db)) >= 0) {
if (next_res_status > 0) {
SET_ERROR_AND_WARN_ONCE(query_error,
"mysql query failed (mysql_next_result): %s",
mysql_error(host->db));
}
do {
MYSQL_RES *temp_result;
/*
* The result must not contain data.
* Keep the first result set. Reject multiple result sets.
*/
else if ((temp_res = mysql_store_result(host->db)) != 0) {
if ((temp_result = mysql_store_result(host->db)) != 0) {
if (first_result == 0) {
first_result = temp_result;
} else {
SET_ERROR_AND_WARN_ONCE(query_error,
"mysql query failed: multiple result sets "
"returning data are not supported");
mysql_free_result(temp_res);
"%s:%s: query failed: multiple result sets "
"returning data are not supported",
dict_mysql->dict.type,
dict_mysql->dict.name);
mysql_free_result(temp_result);
}
}
/*
* There must be no errors: mysql_field_count() must return 0
* to indicate that the "no data" result was expected.
* No result: the mysql_field_count() function must return 0
* to indicate that mysql_store_result() completed normally.
*/
else if (mysql_field_count(host->db) != 0) {
SET_ERROR_AND_WARN_ONCE(query_error,
"mysql query failed (mysql_field_count): %s",
"%s:%s: query failed (mysql_store_result): %s",
dict_mysql->dict.type,
dict_mysql->dict.name,
mysql_error(host->db));
}
/*
* Are there more results? -1 = no, 0 = yes, > 0 = error.
*/
if ((next_res_status = mysql_next_result(host->db)) > 0) {
SET_ERROR_AND_WARN_ONCE(query_error,
"%s:%s: query failed (mysql_next_result): %s",
dict_mysql->dict.type,
dict_mysql->dict.name,
mysql_error(host->db));
}
} while (next_res_status == 0);
/*
* Enforce the require_result_set setting.
*/
if (first_result == 0 && dict_mysql->require_result_set) {
SET_ERROR_AND_WARN_ONCE(query_error,
"%s:%s: query failed: query returned no result set"
"(require_result_set = yes)",
dict_mysql->dict.type,
dict_mysql->dict.name);
}
}
@ -609,13 +617,14 @@ static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
plmysql_down_host(host);
if (errno == 0)
errno = ENOTSUP;
if (res) {
mysql_free_result(res);
res = 0;
if (first_result) {
mysql_free_result(first_result);
first_result = 0;
}
} else {
if (msg_verbose)
msg_info("dict_mysql: successful query result from host %s",
msg_info("%s:%s: successful query result from host %s",
dict_mysql->dict.type, dict_mysql->dict.name,
host->hostname);
event_request_timer(dict_mysql_event, (void *) host,
IDLE_CONN_INTV);
@ -623,7 +632,8 @@ static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
}
}
return res;
*result = first_result;
return (query_error == 0);
}
/*
@ -717,6 +727,7 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1);
#endif
#endif
dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1);
/*
* XXX: The default should be non-zero for safety, but that is not

View File

@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
#define MAIL_RELEASE_DATE "20161225"
#define MAIL_RELEASE_DATE "20161226"
#define MAIL_VERSION_NUMBER "3.2"
#ifdef SNAPSHOT

View File

@ -100,8 +100,8 @@ static const char *pcf_ldap_suffixes[] = {
static const char *pcf_mysql_suffixes[] = {
"additional_conditions", "dbname", "domain", "expansion_limit",
"hosts", "password", "query", "result_format", "select_field",
"table", "user", "where_field", 0,
"hosts", "password", "query", "result_format", "require_result_set",
"select_field", "table", "user", "where_field", 0,
};
/* See pgsql_table(5). */