Hi Wietse,

On Fri, 2013-11-15 at 20:12 -0500, Wietse Venema wrote:
> Gareth Palmer:
> > Hello,
> > 
> > The following patch adds support for using SSL when connecting to a
> > MySQL server and support for reading my.cnf files which can set other
> > connection related options.
> > 
> > New configuration parameters for SSL connections are -
> > 
> >   tls_cert_file   - File containing the client's public key.
> >   tls_key_file    - File containing the client's private key.
> >   tls_CAfile      - File containing the server's public key.
> >   tls_CApath      - Directory containing the server's public key.
> >   tls_ciphers     - A list of permissible ciphers to use for encryption.
> >   tls_verify_cert - Verify that the name of the server matches the
> >                     common name in the certificate.
> > 
> > New configuration parameters for reading my.cnf files are -
> > 
> >   option_file     - Name of the file to read from.
> >   option_group    - Name of the group to read from.
> > 
> > Both are empty by default so no option file will be read from.
> 
> Thanks for cleaning up some of the internal APIs.
> 
> However, please confirm that your code works with configuration
> files that do not set the new parameters.
> 
> In the following code, cfg_get_str() returns a null character pointer
> when a parameter is not specified in the Postfix configuration file:
> 
> +    dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0);
> +    dict_mysql->option_group = cfg_get_str(p, "option_group", NULL, 0, 0);
> +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
> +    dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0);
> +    dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0);
> +    dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0);
> +    dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0);
> +    dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0);
> 
> When the database is closed, the dict_mysql_close() function passes
> these null pointers to myfree(). 
> 
> +     myfree(dict_mysql->option_file);
> +     myfree(dict_mysql->option_group);
> + #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
> +     myfree(dict_mysql->tls_key_file);
> +     myfree(dict_mysql->tls_cert_file);
> +     myfree(dict_mysql->tls_CAfile);
> +     myfree(dict_mysql->tls_CApath);
> +     myfree(dict_mysql->tls_ciphers);
> + #endif
> 
> However.  myfree() does not accept null pointer arguments: It will
> abort the program instead.
> 
> You can test the dict_mysql_close() routine with the "mail_dict"
> test program:
> 
>     $ cd src/global
>     $ make mail_dict
>     $ ./mail_dict mysql:/path/to/file read
>     get foo
>     ^D
> 
> The simplest fix is to replace 
> 
>       myfree(dict_mysql->whatever);
> 
> with
>       if (dict_mysql->whatever)
>           myfree(dict_mysql->whatever);
> 
> I can make that change if you like.

Thanks for reviewing the patch.

Attached is a new patch against 2.11-20131114 with those fixes.

> However it would be good if you can confirm that the code
> works even when none of the new parameters is specified.

I tested using mail_dict with none, some and all of the new parameters,
it doesn't segfault now.

Testing also revealed another bug in the patch when giving a NULL arg to
mysql_options(). Fixed that too.

>       Wietse

diff -urN postfix-2.11-20131114.orig/man/man5/mysql_table.5 postfix-2.11-20131114/man/man5/mysql_table.5
--- postfix-2.11-20131114.orig/man/man5/mysql_table.5	2013-11-16 14:54:05.379045451 +1300
+++ postfix-2.11-20131114/man/man5/mysql_table.5	2013-11-16 15:00:55.751670027 +1300
@@ -256,6 +256,25 @@
 temporary error if the limit is exceeded.  Setting the
 limit to 1 ensures that lookups do not return multiple
 values.
+.IP "\fBtls_cert_file\fR"
+File containing client's X509 certificate.
+.IP "\fBtls_key_file\fR"
+File containing the private key corresponding to \fBtls_cert_file\fR.
+.IP "\fBtls_CAfile\fR"
+File containing certificates for all of the X509 Certificate
+Authorities the client will recognize.  Takes precedence over
+\fBtls_CApath\fR.
+.IP "\fBtls_CApath\fR"
+Directory containing X509 Certificate Authority certificates
+in separate individual files.
+.IP "\fBtls_verify_cert (default: no)\fR"
+Verify that the server's name matches the common name in the
+certficate.
+.IP "\fBoption_file\fR"
+Read options from the given file instead of the default my.cnf
+location.
+.IP "\fBoption_group\fR"
+Read options from the given group.
 .SH "OBSOLETE QUERY INTERFACE"
 .na
 .nf
diff -urN postfix-2.11-20131114.orig/src/global/dict_mysql.c postfix-2.11-20131114/src/global/dict_mysql.c
--- postfix-2.11-20131114.orig/src/global/dict_mysql.c	2013-11-16 14:54:05.371045594 +1300
+++ postfix-2.11-20131114/src/global/dict_mysql.c	2013-11-16 15:00:36.948014146 +1300
@@ -91,6 +91,23 @@
 /*	releases.
 /* .IP hosts
 /*	List of hosts to connect to.
+/* .IP tls_cert_file
+/*      File containing client's X509 certificate.
+/* .IP tls_key_file
+/*      File containing the private key corresponding to \fItls_cert_file\fR.
+/* .IP tls_CAfile
+/*      File containing certificates for all of the X509 Certificate
+/*      Authorities the client will recognize.  Takes precedence over
+/*      \fItls_CApath\fR.
+/* .IP tls_CApath
+/*      Directory containing X509 Certificate Authority certificates
+/*      in separate individual files.
+/* .IP tls_verify_cert
+/*      Verify that the server's name matches the common name of the certficate.
+/* .IP option_file
+/*      Read options from the given file instead of the default my.cnf location.
+/* .IP option_group
+/*      Read options from the given group.
 /* .PP
 /*	For example, if you want the map to reference databases of
 /*	the name "your_db" and execute a query like this: select
@@ -217,6 +234,8 @@
     CFG_PARSER *parser;
     char   *query;
     char   *result_format;
+    char   *option_file;
+    char   *option_group;
     void   *ctx;
     int     expansion_limit;
     char   *username;
@@ -226,6 +245,14 @@
     PLMYSQL *pldb;
 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
     HOST   *active_host;
+    char   *tls_cert_file;
+    char   *tls_key_file;
+    char   *tls_CAfile;
+    char   *tls_CApath;
+    char   *tls_ciphers;
+#if MYSQL_VERSION_ID >= 50023
+    int    tls_verify_cert;
+#endif
 #endif
 } DICT_MYSQL;
 
@@ -242,12 +269,11 @@
 
 /* internal function declarations */
 static PLMYSQL *plmysql_init(ARGV *);
-static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *,
-				        char *, char *);
+static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *);
 static void plmysql_dealloc(PLMYSQL *);
 static void plmysql_close_host(HOST *);
 static void plmysql_down_host(HOST *);
-static void plmysql_connect_single(HOST *, char *, char *, char *);
+static void plmysql_connect_single(DICT_MYSQL *, HOST *);
 static const char *dict_mysql_lookup(DICT *, const char *);
 DICT   *dict_mysql_open(const char *, int, int);
 static void dict_mysql_close(DICT *);
@@ -349,10 +375,7 @@
 	return (0);
 
     /* do the query - set dict->error & 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) {
+    if ((query_res = plmysql_query(dict_mysql, name, query)) == 0) {
 	dict->error = DICT_ERR_RETRY;
 	return (0);
     }
@@ -428,10 +451,10 @@
 
 /* dict_mysql_get_active - get an active connection */
 
-static HOST *dict_mysql_get_active(PLMYSQL *PLDB, char *dbname,
-				           char *username, char *password)
+static HOST *dict_mysql_get_active(DICT_MYSQL *dict_mysql)
 {
     const char *myname = "dict_mysql_get_active";
+    PLMYSQL *PLDB = dict_mysql->pldb;
     HOST   *host;
     int     count = RETRY_CONN_MAX;
 
@@ -457,7 +480,7 @@
 	if (msg_verbose)
 	    msg_info("%s: attempting to connect to host %s", myname,
 		     host->hostname);
-	plmysql_connect_single(host, dbname, username, password);
+	plmysql_connect_single(dict_mysql, host);
 	if (host->stat == STATACTIVE)
 	    return host;
     }
@@ -485,17 +508,12 @@
 
 static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
 				        const char *name,
-				        VSTRING *query,
-				        char *dbname,
-				        char *username,
-				        char *password)
+				        VSTRING *query)
 {
-    PLMYSQL *PLDB = dict_mysql->pldb;
     HOST   *host;
     MYSQL_RES *res = 0;
 
-    while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
-
+    while ((host = dict_mysql_get_active(dict_mysql)) != NULL) {
 #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
 
 	/*
@@ -534,15 +552,30 @@
  * 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
  */
-static void plmysql_connect_single(HOST *host, char *dbname, char *username, char *password)
+static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host)
 {
     if ((host->db = mysql_init(NULL)) == NULL)
 	msg_fatal("dict_mysql: insufficient memory");
+    mysql_options(host->db, MYSQL_READ_DEFAULT_FILE, dict_mysql->option_file);
+    mysql_options(host->db, MYSQL_READ_DEFAULT_GROUP, dict_mysql->option_group);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+    if (dict_mysql->tls_key_file || dict_mysql->tls_cert_file ||
+	dict_mysql->tls_CAfile || dict_mysql->tls_CApath || dict_mysql->tls_ciphers)
+	mysql_ssl_set(host->db,
+		      dict_mysql->tls_key_file, dict_mysql->tls_cert_file,
+		      dict_mysql->tls_CAfile, dict_mysql->tls_CApath,
+		      dict_mysql->tls_ciphers);
+#if MYSQL_VERSION_ID >= 50023
+    if (dict_mysql->tls_verify_cert != -1)
+	mysql_options(host->db, MYSQL_OPT_SSL_VERIFY_SERVER_CERT,
+		      &dict_mysql->tls_verify_cert);
+#endif
+#endif
     if (mysql_real_connect(host->db,
 			   (host->type == TYPEINET ? host->name : 0),
-			   username,
-			   password,
-			   dbname,
+			   dict_mysql->username,
+			   dict_mysql->password,
+			   dict_mysql->dbname,
 			   host->port,
 			   (host->type == TYPEUNIX ? host->name : 0),
 			   0)) {
@@ -582,7 +615,7 @@
 
 static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
 {
-    const char *myname = "mysqlname_parse";
+    const char *myname = "mysql_parse_config";
     CFG_PARSER *p = dict_mysql->parser;
     VSTRING *buf;
     char   *hosts;
@@ -591,6 +624,18 @@
     dict_mysql->password = cfg_get_str(p, "password", "", 0, 0);
     dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
     dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0);
+    dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0);
+    dict_mysql->option_group = cfg_get_str(p, "option_group", NULL, 0, 0);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+    dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0);
+    dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0);
+    dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0);
+    dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0);
+    dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0);
+#if MYSQL_VERSION_ID >= 50023
+    dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1);
+#endif
+#endif
 
     /*
      * XXX: The default should be non-zero for safety, but that is not
@@ -759,6 +804,22 @@
     myfree(dict_mysql->dbname);
     myfree(dict_mysql->query);
     myfree(dict_mysql->result_format);
+    if (dict_mysql->option_file)
+    	myfree(dict_mysql->option_file);
+    if (dict_mysql->option_group)
+	myfree(dict_mysql->option_group);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+    if (dict_mysql->tls_key_file)
+	myfree(dict_mysql->tls_key_file);
+    if (dict_mysql->tls_cert_file)
+	myfree(dict_mysql->tls_cert_file);
+    if (dict_mysql->tls_CAfile)
+	myfree(dict_mysql->tls_CAfile);
+    if (dict_mysql->tls_CApath)
+	myfree(dict_mysql->tls_CApath);
+    if (dict_mysql->tls_ciphers)
+	myfree(dict_mysql->tls_ciphers);
+#endif
     if (dict_mysql->hosts)
 	argv_free(dict_mysql->hosts);
     if (dict_mysql->ctx)

Reply via email to