Files: tls_client.c, tls_server.c, tls_session.c, tls_misc.c,
tls_verify.c.
+20050922
+
+ Bugfix: the *SQL clients did not uniformly choose the
+ database host from the available pool of servers due to an
+ off-by-one error, so that the "last" available server was
+ not selected. Leandro Santi. Files: dict_mysql.c, dict_pgsql.c.
+
+ Update: common code factored out into db_common.c, and
+ adoption of Liviu Daia's connection aware MySQL quoting.
+ Files: dict_ldap.c, dict_mysql.c, dict_pgsql.c, db_common.c.
+
Open problems:
Look for systems with XPG basename() declared in <libgen.h>,
var_max_queue_time / 86400.0);
} else if (bounce_info->report_type == BOUNCE_REPORT_SUCCESS) {
post_mail_fputs(bounce,
- "Your message was sucessfully delivered to the destination(s) listed");
+ "Your message was successfully delivered to the destination(s) listed");
post_mail_fputs(bounce,
"below. In the case of delivery to mailbox you will receive no further");
post_mail_fputs(bounce,
#define DB_COMMON_KEY_USER (1 << 1) /* Need lookup key localpart */
#define DB_COMMON_VALUE_DOMAIN (1 << 2) /* Need result domain */
#define DB_COMMON_VALUE_USER (1 << 3) /* Need result localpart */
+#define DB_COMMON_KEY_PARTIAL (1 << 4) /* Key uses input substrings */
typedef struct {
DICT *dict;
+ STRING_LIST *domain;
int flags;
int nparts;
} DB_COMMON_CTX;
if (ctx == 0) {
ctx = (DB_COMMON_CTX *)(*ctxPtr = mymalloc(sizeof *ctx));
ctx->dict = dict;
+ ctx->domain = 0;
ctx->flags = 0;
ctx->nparts = 0;
}
case '%':
break;
case 'u':
- ctx->flags |=
- query ? DB_COMMON_KEY_USER : DB_COMMON_VALUE_USER;
+ ctx->flags |=
+ query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_USER;
dynamic = 1;
break;
case 'd':
- ctx->flags |=
- query ? DB_COMMON_KEY_DOMAIN : DB_COMMON_VALUE_DOMAIN;
+ ctx->flags |=
+ query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_DOMAIN;
dynamic = 1;
break;
case 's': case 'S':
dynamic = 1;
break;
case 'U':
- ctx->flags |= DB_COMMON_KEY_USER;
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
dynamic = 1;
break;
case '1': case '2': case '3': case '4': case '5':
ctx->nparts = *cp - '0';
/* FALLTHROUGH */
case 'D':
- ctx->flags |= DB_COMMON_KEY_DOMAIN;
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
dynamic = 1;
break;
default:
return dynamic;
}
+/* db_common_parse_domain - parse domain matchlist*/
+
+void db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxPtr;
+ char *domainlist;
+ char *myname = "db_common_parse_domain";
+
+ domainlist = cfg_get_str(parser, "domain", "", 0, 0);
+ if (*domainlist) {
+ ctx->domain = string_list_init(MATCH_FLAG_NONE, domainlist);
+ if (ctx->domain == 0)
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, parser->name, domainlist);
+ }
+ myfree(domainlist);
+}
+
+/* db_common_dict_partial - Does query use partial lookup keys? */
+
+int db_common_dict_partial(void *ctxPtr)
+{
+#if 0 /* Breaks recipient_delimiter */
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxPtr;
+
+ return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
+#endif
+ return (0);
+}
+
/* db_common_free_ctx - free parse context */
void db_common_free_ctx(void *ctxPtr)
{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxPtr;
+
+ if (ctx->domain)
+ string_list_free(ctx->domain);
myfree((char *)ctxPtr);
}
/* db_common_check_domain - check domain list */
-int db_common_check_domain(STRING_LIST *domain_list, const char *addr)
+int db_common_check_domain(void *ctxPtr, const char *addr)
{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *)ctxPtr;
char *domain;
- if (domain_list) {
+ if (ctx->domain) {
if ((domain = strrchr(addr, '@')) != NULL)
++domain;
if (domain == NULL || domain == addr + 1)
return (0);
- if (match_list_match(domain_list, domain) == 0)
+ if (match_list_match(ctx->domain, domain) == 0)
return (0);
}
return (1);
typedef void (*db_quote_callback_t)(DICT *, const char *, VSTRING *);
extern int db_common_parse(DICT *, void **, const char *, int);
-extern void db_common_free_ctx(void *);
+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 *,
const char *, VSTRING *, db_quote_callback_t);
-extern int db_common_check_domain(STRING_LIST *, const char *);
+extern int db_common_check_domain(void *, const char *);
+extern void db_common_free_ctx(void *);
extern void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser);
/* LICENSE
CFG_PARSER *parser; /* common parameter parser */
char *query; /* db_common_expand() query */
char *result_format; /* db_common_expand() result_format */
- STRING_LIST *domain; /* restrict queries to these domains */
void *ctx; /* db_common_parse() context */
int dynamic_base; /* Search base has substitutions? */
int expansion_limit;
* addresses in domains on the list. This can significantly reduce the
* load on the LDAP server.
*/
- if (db_common_check_domain(dict_ldap->domain, name) == 0) {
+ if (db_common_check_domain(dict_ldap->ctx, name) == 0) {
if (msg_verbose)
msg_info("%s: Skipping lookup of '%s'", myname, name);
return (0);
cfg_parser_free(dict_ldap->parser);
myfree(dict_ldap->server_host);
myfree(dict_ldap->search_base);
- if (dict_ldap->domain)
- string_list_free(dict_ldap->domain);
myfree(dict_ldap->query);
if (dict_ldap->result_format)
myfree(dict_ldap->result_format);
char *s;
char *h;
char *server_host;
- char *domainlist;
char *scope;
char *attr;
int tmp;
sizeof(*dict_ldap));
dict_ldap->dict.lookup = dict_ldap_lookup;
dict_ldap->dict.close = dict_ldap_close;
- dict_ldap->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ dict_ldap->dict.flags = dict_flags;
dict_ldap->ld = NULL;
dict_ldap->parser = cfg_parser_alloc(ldapsource);
dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base",
"", 0, 0);
- domainlist = cfg_get_str(dict_ldap->parser, "domain", "", 0, 0);
- if (*domainlist) {
- dict_ldap->domain = string_list_init(MATCH_FLAG_NONE, domainlist);
- if (dict_ldap->domain == NULL)
- /*
- * The "domain" optimization skips input keys that may in fact
- * have unwanted matches in the database, so failure to create
- * the match list is fatal.
- */
- msg_fatal("%s: %s: domain match list creation using '%s' failed",
- myname, ldapsource, domainlist);
- } else {
- dict_ldap->domain = NULL;
- }
- myfree(domainlist);
-
/*
* get configured value of "timeout"; default to 10 seconds
*
myname, ldapsource, dict_ldap->query);
}
(void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format, 0);
+ db_common_parse_domain(dict_ldap->parser, dict_ldap->ctx);
+
+ /*
+ * Maps that use substring keys should only be used with the full
+ * input key.
+ */
+ if (db_common_dict_partial(dict_ldap->ctx))
+ dict_ldap->dict.flags |= DICT_FLAG_PATTERN;
+ else
+ dict_ldap->dict.flags |= DICT_FLAG_FIXED;
attr = cfg_get_str(dict_ldap->parser, "result_attribute",
"maildrop", 0, 0);
CFG_PARSER *parser;
char *query;
char *result_format;
- STRING_LIST *domain;
void *ctx;
int expansion_limit;
char *username;
char *dbname;
ARGV *hosts;
PLMYSQL *pldb;
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ HOST *active_host;
+#endif
} DICT_MYSQL;
#define STATACTIVE (1<<0)
/* internal function declarations */
static PLMYSQL *plmysql_init(ARGV *);
-static MYSQL_RES *plmysql_query(PLMYSQL *, const char *, char *, char *, char *);
+static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *,
+ char *, char *);
static void plmysql_dealloc(PLMYSQL *);
static void plmysql_close_host(HOST *);
static void plmysql_down_host(HOST *);
msg_panic("dict_mysql_quote: integer overflow in 2*%d+1", len);
VSTRING_SPACE(result, buflen);
- /*
- * XXX Too expensive to find out which connection is still open at
- * this point. Grrr!
- */
-#if 0 && defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
- mysql_real_escape_string(dict_mysql->pldb->db_hosts[i].db,
- vstring_end(result), name, len);
-#else
- mysql_escape_string(vstring_end(result), name, len);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ if (dict_mysql->active_host)
+ mysql_real_escape_string(dict_mysql->active_host->db,
+ vstring_end(result), name, len);
+ else
#endif
+ mysql_escape_string(vstring_end(result), name, len);
+
VSTRING_SKIP(result);
}
int numrows;
int expansion;
const char *r;
+ db_quote_callback_t quote_func = dict_mysql_quote;
dict_errno = 0;
/*
* 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. Do not try "@domain" keys.
+ * the load on the server.
*/
- if (db_common_check_domain(dict_mysql->domain, name) == 0) {
+ if (db_common_check_domain(dict_mysql->ctx, name) == 0) {
if (msg_verbose)
msg_info("%s: Skipping lookup of '%s'", myname, name);
return (0);
/*
* 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, dict_mysql_quote))
+ name, 0, query, quote_func))
return (0);
/* do the query - set dict_errno & cleanup if there's an error */
- if ((query_res = plmysql_query(pldb, vstring_str(query),
+ if ((query_res = plmysql_query(dict_mysql, name, query,
dict_mysql->dbname,
dict_mysql->username,
dict_mysql->password)) == 0) {
}
if (count) {
- /*
- * Calling myrand() can deplete the random pool.
- * Don't rely on the optimizer to weed out the call
- * when count == 1.
- */
- idx = (count > 1) ? 1 + (count - 1) * (double) myrand() / RAND_MAX : 1;
+ idx = (count > 1) ?
+ 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
for (i = 0; i < PLDB->len_hosts; i++) {
if (dict_mysql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
- --idx == 0)
+ --idx == 0)
return PLDB->db_hosts[i];
}
}
* close unnecessary active connections
*/
-static MYSQL_RES *plmysql_query(PLMYSQL *PLDB,
- const char *query,
+static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql,
+ const char *name,
+ VSTRING *query,
char *dbname,
char *username,
char *password)
{
+ PLMYSQL *PLDB = dict_mysql->pldb;
HOST *host;
MYSQL_RES *res = 0;
while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) {
- if (!(mysql_query(host->db, query))) {
+
+#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, dict_mysql->query,
+ name, 0, query, dict_mysql_quote);
+ dict_mysql->active_host = 0;
+#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));
plmysql_down_host(host);
VSTRING *buf;
int i;
char *hosts;
- char *domain;
p = dict_mysql->parser = cfg_parser_alloc(mysqlcf);
dict_mysql->username = cfg_get_str(p, "user", "", 0, 0);
(void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx,
dict_mysql->query, 1);
(void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0);
+ db_common_parse_domain(p, dict_mysql->ctx);
- domain = cfg_get_str(p, "domain", "", 0, 0);
- if (*domain) {
- if (!(dict_mysql->domain = string_list_init(MATCH_FLAG_NONE, domain)))
- /*
- * The "domain" optimization skips input keys that may in fact
- * have unwanted matches in the database, so failure to create
- * the match list is fatal.
- */
- msg_fatal("%s: %s: domain match list creation using '%s' failed",
- myname, mysqlcf, domain);
- }
+ /*
+ * Maps that use substring keys should only be used with the full
+ * input key.
+ */
+ if (db_common_dict_partial(dict_mysql->ctx))
+ dict_mysql->dict.flags |= DICT_FLAG_PATTERN;
else
- dict_mysql->domain = 0;
- myfree(domain);
+ dict_mysql->dict.flags |= DICT_FLAG_FIXED;
hosts = cfg_get_str(p, "hosts", "", 0, 0);
sizeof(DICT_MYSQL));
dict_mysql->dict.lookup = dict_mysql_lookup;
dict_mysql->dict.close = dict_mysql_close;
- dict_mysql->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ dict_mysql->dict.flags = dict_flags;
mysql_parse_config(dict_mysql, name);
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000
+ dict_mysql->active_host = 0;
+#endif
dict_mysql->pldb = plmysql_init(dict_mysql->hosts);
if (dict_mysql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
myfree(dict_mysql->dbname);
myfree(dict_mysql->query);
myfree(dict_mysql->result_format);
- if (dict_mysql->domain)
- string_list_free(dict_mysql->domain);
if (dict_mysql->hosts)
argv_free(dict_mysql->hosts);
if (dict_mysql->ctx)
CFG_PARSER *parser;
char *query;
char *result_format;
- STRING_LIST *domain;
void *ctx;
int expansion_limit;
char *username;
/*
* 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. Do not try "@domain" keys.
+ * the load on the server.
*/
- if (db_common_check_domain(dict_pgsql->domain, name) == 0) {
+ if (db_common_check_domain(dict_pgsql->ctx, name) == 0) {
if (msg_verbose)
msg_info("%s: Skipping lookup of '%s'", myname, name);
return (0);
}
if (count) {
- /*
- * Calling myrand() can deplete the random pool.
- * Don't rely on the optimizer to weed out the call
- * when count == 1.
- */
- idx = (count > 1) ? 1 + (count - 1) * (double) myrand() / RAND_MAX : 1;
+ idx = (count > 1) ?
+ 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1;
for (i = 0; i < PLDB->len_hosts; i++) {
if (dict_pgsql_check_stat(PLDB->db_hosts[i], stat, type, t) &&
- --idx == 0)
+ --idx == 0)
return PLDB->db_hosts[i];
}
}
char *hosts;
VSTRING *query;
char *select_function;
- char *domain;
p = dict_pgsql->parser = cfg_parser_alloc(pgsqlcf);
dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
(void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
dict_pgsql->query, 1);
(void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
+ db_common_parse_domain(p, dict_pgsql->ctx);
- domain = cfg_get_str(p, "domain", "", 0, 0);
- if (*domain) {
- if (!(dict_pgsql->domain = string_list_init(MATCH_FLAG_NONE, domain)))
- /*
- * The "domain" optimization skips input keys that may in fact
- * have unwanted matches in the database, so failure to create
- * the match list is fatal.
- */
- msg_fatal("%s: %s: domain match list creation using '%s' failed",
- myname, pgsqlcf, domain);
- }
+ /*
+ * Maps that use substring keys should only be used with the full
+ * input key.
+ */
+ if (db_common_dict_partial(dict_pgsql->ctx))
+ dict_pgsql->dict.flags |= DICT_FLAG_PATTERN;
else
- dict_pgsql->domain = 0;
- myfree(domain);
+ dict_pgsql->dict.flags |= DICT_FLAG_FIXED;
hosts = cfg_get_str(p, "hosts", "", 0, 0);
sizeof(DICT_PGSQL));
dict_pgsql->dict.lookup = dict_pgsql_lookup;
dict_pgsql->dict.close = dict_pgsql_close;
+ dict_pgsql->dict.flags = dict_flags;
pgsql_parse_config(dict_pgsql, name);
dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
- dict_pgsql->dict.flags = dict_flags | DICT_FLAG_FIXED;
if (dict_pgsql->pldb == NULL)
msg_fatal("couldn't intialize pldb!\n");
return &dict_pgsql->dict;
myfree(dict_pgsql->dbname);
myfree(dict_pgsql->query);
myfree(dict_pgsql->result_format);
- if (dict_pgsql->domain)
- string_list_free(dict_pgsql->domain);
if (dict_pgsql->hosts)
argv_free(dict_pgsql->hosts);
if (dict_pgsql->ctx)
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20050920"
+#define MAIL_RELEASE_DATE "20050922"
#define MAIL_VERSION_NUMBER "2.3"
#ifdef SNAPSHOT
return (cn);
}
-/* tls_text_name - extract issuer common name from certificate */
+/* tls_issuer_CN - extract issuer common name from certificate */
char *tls_issuer_CN(X509 *peer)
{