On 11/27/2016 01:47 PM, John Fawcett wrote:
> On 11/23/2016 10:54 PM, [email protected] wrote:
>> On 2016-11-23 21:57, John Fawcett wrote:
>>> On 11/22/2016 01:35 AM, Joel Linn wrote:
>>>> Hey Guys,
>>>>
>>>> this issue has decayed a bit but I now finally found the time (and the
>>>> nerves) to integrate the fix in my system.
>>>> I'm running Ubuntu 16.04 and trying not change to many things and be
>>>> able to have clean comparison I applied the patch to the apt sources
>>>> and only replaced the postfix-mysql package (ubuntu is using 3.1.0 it
>>>> seems) with my own.
>>>> I introduced some stored procedures and from my tests I conclude it
>>>> works. I will see in the next couple of days if there are issues I
>>>> overlooked.
>>>> I ran into an issue where I was returning no result set using
>>>> "smtpd_recipient_restrictions = check_recipient_access mysql:/[...]".
>>>> I'm not sure if that's an dict_mysql issue or caused by design
>>>> upstream. I overcame that by doing "select 1 from dual where false"
>>>> when I don't have anything else to return, which basically is an empty
>>>> result set.
>>>>
>>>> Thanks very much for your work,
>>>> Joel
>>> Joel
>>>
>>> can you provide some more information to reproduce the issue you
>>> mentioned?
>>>
>>> John
>> Sure I can. Have a look at my log:
>>
>> Nov 23 22:42:49 leuchtkanone postfix/smtpd[5863]: connect from
>> divmail1.helmholtz-berlin.de[134.30.104.21]
>> Nov 23 22:42:49 leuchtkanone postfix/smtpd[5863]: warning:
>> mysql:/etc/postfix/sql/recipient-access.cf: table lookup problem
>> Nov 23 22:42:49 leuchtkanone postfix/smtpd[5863]: NOQUEUE: reject:
>> RCPT from divmail1.helmholtz-berlin.de[134.30.104.21]: 451 4.3.5
>> <[email protected]>: Recipient address rejected: Server configuration
>> error; from=<[email protected]> to=<[email protected]>
>> proto=ESMTP helo=<divmail1.helmholtz-berlin.de>
>>
>> I used this empty test procedure (query = CALL ret_empty;):
>>
>> CREATE DEFINER=`vmail`@`localhost` PROCEDURE `ret_empty`()
>> BEGIN
>> END
>>
>> In my procedure I had some code paths return a result and some not.
>> Like I said i'm now using "SELECT 1 FROM dual WHERE false;" for those
>> dead paths, this simulates the behavior of the previous solution (one
>> line query) which always returns a result set, even if it's empty.
> Joel
>
> with MySQL procedures every SELECT statement that is executed produces a
> result set. In my patch I explicitly check to make sure there are no
> multiple result sets, but I do not check for no result. That could be
> improved upon, to give a specific warning that no result sets were
> returned. However, that will always be a lookup failure. The stored
> procedure (based on the way the patch is written) must always return a
> result set even if that result set is empty.
>
> Your solution with returning an empty result set when there is no other
> result (SELECT 1 FROM DUAL WHERE FALSE) looks correct.
>
> John
>
Revised patch to improve error reporting when no result set containing
data is returned
diff -ur postfix-3.2-20161106-orig/proto/mysql_table
postfix-3.2-20161106/proto/mysql_table
--- postfix-3.2-20161106-orig/proto/mysql_table 2016-10-01
15:44:52.000000000 +0200
+++ postfix-3.2-20161106/proto/mysql_table 2016-11-27
10:08:05.535236131 +0100
@@ -289,6 +289,39 @@
# certificate.
# .sp
# This parameter is available with Postfix 2.11 and later.
+# USING MYSQL STORED PROCEDURES
+# .ad
+# .fi
+# With Postfix 3.2 it is possible to call a stored procedure
+# instead of using a SELECT statement in the query, e.g.
+#
+# .nf
+# query = CALL lookup('%s')
+# .fi
+#
+# The preivously described '%' expansions can be used in the
+# parameter(s) to the stored procedure.
+#
+# The stored procedure must return data with a single result
+# set. Multiple result sets are not supported and will
+# produce a warning and no valid result. 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.
+#
+# The following is an example of a stored procedure returning
+# data with a single result set:
+#
+# .nf
+# CREATE [DEFINER=`user`@`host`] PROCEDURE
+# `lookup`(IN `param` VARCHAR(255))
+# READS SQL DATA
+# SQL SECURITY INVOKER
+# BEGIN
+# select goto from alias where address=param;
+# END
+# .fi
# OBSOLETE QUERY INTERFACE
# .ad
# .fi
diff -ur postfix-3.2-20161106-orig/src/global/dict_mysql.c
postfix-3.2-20161106/src/global/dict_mysql.c
--- postfix-3.2-20161106-orig/src/global/dict_mysql.c 2016-09-25
17:27:11.000000000 +0200
+++ postfix-3.2-20161106/src/global/dict_mysql.c 2016-11-27
13:56:50.956334035 +0100
@@ -187,6 +187,7 @@
#include <time.h>
#include <mysql.h>
#include <limits.h>
+#include <errno.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
@@ -519,6 +520,10 @@
{
HOST *host;
MYSQL_RES *res = 0;
+ MYSQL_RES *temp_res = 0;
+ int status=0;
+ int num_rs=0;
+ int sp_error=0;
while ((host = dict_mysql_get_active(dict_mysql)) != NULL) {
#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
@@ -536,9 +541,46 @@
#endif
if (!(mysql_query(host->db, vstring_str(query)))) {
- if ((res = mysql_store_result(host->db)) == 0) {
- msg_warn("mysql query failed: %s", mysql_error(host->db));
+ sp_error = 0;
+ num_rs = 0;
+ res = 0;
+ do {
+ temp_res = mysql_store_result(host->db);
+ /* did statement return data? */
+ if (temp_res) {
+ num_rs++;
+ if (num_rs > 1) {
+ msg_warn("dict_mysql: multiple result sets
returning data are not supported");
+ mysql_free_result(temp_res);
+ temp_res = 0;
+ } else {
+ res = temp_res;
+ }
+ } else {
+ /* no data, check if there were errors */
+ if (mysql_field_count(host->db) == 0) {
+ if (num_rs == 0) {
+ msg_warn("dict_mysql: stored procedure did
not return any result set containing data");
+ } else {
+ if (msg_verbose)
+ msg_info("dict_mysql: successful final
result for stored procedure");
+ }
+ } else {
+ sp_error=1;
+ msg_warn("dict_mysql: unsuccessful final result
for stored procedure: %s", mysql_error(host->db));
+ }
+ }
+ /* more results? -1 = no, >0 = error, 0 = yes (keep
looping) */
+ if ((status = mysql_next_result(host->db)) > 0)
+ msg_warn("mysql query failed (mysql_next_result):
%s", mysql_error(host->db));
+ } while (status == 0);
+ if (status != -1 || sp_error || num_rs != 1) {
plmysql_down_host(host);
+ errno = ENOTSUP;
+ if (res) {
+ mysql_free_result(res);
+ res = 0;
+ }
} else {
if (msg_verbose)
msg_info("dict_mysql: successful query from host
%s", host->hostname);
@@ -587,7 +629,7 @@
dict_mysql->dbname,
host->port,
(host->type == TYPEUNIX ? host->name : 0),
- 0)) {
+ CLIENT_MULTI_RESULTS)) {
if (msg_verbose)
msg_info("dict_mysql: successful connection to host %s",
host->hostname);