diff --git a/postfix/HISTORY b/postfix/HISTORY
index 133488489..d736e68fa 100644
--- a/postfix/HISTORY
+++ b/postfix/HISTORY
@@ -28439,3 +28439,11 @@ Apologies for any names omitted.
Documentation: in a pgsql: client configuration, the setting
"dbname" is required, but ignored when the setting "hosts"
contains an URI with a database name. File: proto/pgsql_table.
+
+20241025
+
+ Cleanup: accept any well-formed URI prefix as a pgsql: client
+ connection target (the PostgreSQL URI parser decides what
+ is allowed). The dbname setting is now optional if the hosts
+ setting specifies only URIs. Files: util/valid_uri_scheme.[hc],
+ proto/pgsql_table.
diff --git a/postfix/html/pgsql_table.5.html b/postfix/html/pgsql_table.5.html
index e300bcb8d..f55724ed9 100644
--- a/postfix/html/pgsql_table.5.html
+++ b/postfix/html/pgsql_table.5.html
@@ -43,53 +43,64 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
PGSQL PARAMETERS
hosts The hosts that Postfix will try to connect to and query from.
- Besides a postgresql:// connection URI, this setting supports
- the historical forms unix:/pathname for UNIX-domain sockets and
+ Besides a PostgreSQL connection URI, this setting supports the
+ historical forms unix:/pathname for UNIX-domain sockets and
inet:host:port for TCP connections, where the unix: and inet:
prefixes are accepted and ignored for backwards compatibility.
Examples:
hosts = postgresql://username@example.com/databasename?sslmode=require
+ hosts = postgres://user:secret@localhost
hosts = inet:host1.some.domain inet:host2.some.domain:port
hosts = host1.some.domain host2.some.domain:port
hosts = unix:/file/name
- The hosts are tried in random order. The connections are auto-
- matically closed after being idle for about 1 minute, and are
- re-opened as necessary.
+ See https://www.postgresql.org/docs/current/libpq-connect.html
+ for the supported connection URI syntax.
- NOTE: if "hosts" specifies one load balancer and no alternative
- servers, specify the load balancer multiple times in the "hosts"
- line. Without the duplicate info, the Postfix PostgreSQL client
- would not reconnect immediately to the same load balancer after
+ The hosts are tried in random order. The connections are auto-
+ matically closed after being idle for about 1 minute, and are
+ re-opened as necessary. See idle_interval for details.
+
+ NOTE: if hosts specifies a PostgreSQL connection URI, the Post-
+ greSQL client library will ignore the dbname setting for that
+ connection.
+
+ NOTE: if hosts specifies one load balancer and no alternative
+ servers, specify the load balancer multiple times in the hosts
+ line. Without the duplicate info, the Postfix PostgreSQL client
+ would not reconnect immediately to the same load balancer after
a PostgreSQL server failure.
user
password
- The user name and password to log into the pgsql server. Exam-
+ The user name and password to log into the pgsql server. Exam-
ple:
user = someone
password = some_password
- dbname (required)
- The database name on the servers. Example:
+ dbname The database name on the servers. Example:
dbname = customer_database
- This setting is required, but ignored when a postgresql:// URI
- specifies a database name.
+ The dbname setting is ignored for hosts connections that are
+ specified as an URI.
+
+ The dbname setting is required with Postfix 3.10 and later, when
+ hosts specifies any non-URI connection; it is always required
+ with earlier Postfix versions.
encoding
- The encoding used by the database client. The default setting
+ The encoding used by the database client. The default setting
is:
encoding = UTF8
- Historically, the database client was hard coded to use LATIN1
+ Historically, the database client was hard coded to use LATIN1
in an attempt to disable multibyte character support.
This feature is available in Postfix 3.8 and later.
idle_interval (default: 60)
- The number of seconds after which an idle database connection
+ The number of seconds after which an idle database connection
will be closed.
This feature is available in Postfix 3.9 and later.
@@ -100,8 +111,8 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
This feature is available in Postfix 3.9 and later.
- query The SQL query template used to search the database, where %s is
- a substitute for the address Postfix is trying to resolve, e.g.
+ query The SQL query template used to search the database, where %s is
+ a substitute for the address Postfix is trying to resolve, e.g.
query = SELECT replacement FROM aliases WHERE mailbox = '%s'
This parameter supports the following '%' expansions:
@@ -109,48 +120,48 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
%% This is replaced by a literal '%' character. (Postfix 2.2
and later)
- %s This is replaced by the input key. SQL quoting is used
- to make sure that the input key does not add unexpected
+ %s This is replaced by the input key. SQL quoting is used
+ to make sure that the input key does not add unexpected
metacharacters.
%u When the input key is an address of the form user@domain,
- %u is replaced by the SQL quoted local part of the
- address. Otherwise, %u is replaced by the entire search
- string. If the localpart is empty, the query is sup-
+ %u is replaced by the SQL quoted local part of the
+ address. Otherwise, %u is replaced by the entire search
+ string. If the localpart is empty, the query is sup-
pressed and returns no results.
%d When the input key is an address of the form user@domain,
- %d is replaced by the SQL quoted domain part of the
- address. Otherwise, the query is suppressed and returns
+ %d is replaced by the SQL quoted domain part of the
+ address. Otherwise, the query is suppressed and returns
no results.
%[SUD] The upper-case equivalents of the above expansions behave
- in the query parameter identically to their lower-case
- counter-parts. With the result_format parameter (see
- below), they expand the input key rather than the result
+ in the query parameter identically to their lower-case
+ counter-parts. With the result_format parameter (see
+ below), they expand the input key rather than the result
value.
- The above %S, %U and %D expansions are available with
+ The above %S, %U and %D expansions are available with
Postfix 2.2 and later
- %[1-9] The patterns %1, %2, ... %9 are replaced by the corre-
- sponding most significant component of the input key's
- domain. If the input key is user@mail.example.com, then
+ %[1-9] The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is user@mail.example.com, then
%1 is com, %2 is example and %3 is mail. If the input key
- is unqualified or does not have enough domain components
- to satisfy all the specified patterns, the query is sup-
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the query is sup-
pressed and returns no results.
- The above %1, ... %9 expansions are available with Post-
+ The above %1, ... %9 expansions are available with Post-
fix 2.2 and later
- The domain parameter described below limits the input keys to
- addresses in matching domains. When the domain parameter is
+ The domain parameter described below limits the input keys to
+ addresses in matching domains. When the domain parameter is
non-empty, SQL queries for unqualified addresses or addresses in
non-matching domains are suppressed and return no results.
- The precedence of this parameter has changed with Postfix 2.2,
- in prior releases the precedence was, from highest to lowest,
+ The precedence of this parameter has changed with Postfix 2.2,
+ in prior releases the precedence was, from highest to lowest,
select_function, query, select_field, ...
With Postfix 2.2 the query parameter has highest precedence, see
@@ -160,42 +171,42 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
result_format (default: %s)
Format template applied to result attributes. Most commonly used
- to append (or prepend) text to the result. This parameter sup-
+ to append (or prepend) text to the result. This parameter sup-
ports the following '%' expansions:
%% This is replaced by a literal '%' character.
- %s This is replaced by the value of the result attribute.
+ %s This is replaced by the value of the result attribute.
When result is empty it is skipped.
%u When the result attribute value is an address of the form
- user@domain, %u is replaced by the local part of the
- address. When the result has an empty localpart it is
+ user@domain, %u is replaced by the local part of the
+ address. When the result has an empty localpart it is
skipped.
- %d When a result attribute value is an address of the form
- user@domain, %d is replaced by the domain part of the
- attribute value. When the result is unqualified it is
+ %d When a result attribute value is an address of the form
+ user@domain, %d is replaced by the domain part of the
+ attribute value. When the result is unqualified it is
skipped.
%[SUD1-9]
- The upper-case and decimal digit expansions interpolate
- the parts of the input key rather than the result. Their
- behavior is identical to that described with query, and
- in fact because the input key is known in advance,
- queries whose key does not contain all the information
- specified in the result template are suppressed and
+ The upper-case and decimal digit expansions interpolate
+ the parts of the input key rather than the result. Their
+ behavior is identical to that described with query, and
+ in fact because the input key is known in advance,
+ queries whose key does not contain all the information
+ specified in the result template are suppressed and
return no results.
For example, using "result_format = smtp:[%s]" allows one to use
a mailHost attribute as the basis of a transport(5) table. After
- applying the result format, multiple values are concatenated as
+ applying the result format, multiple values are concatenated as
comma separated strings. The expansion_limit and parameter
- explained below allows one to restrict the number of values in
+ explained below allows one to restrict the number of values in
the result, which is especially useful for maps that must return
at most one value.
- The default value %s specifies that each result value should be
+ The default value %s specifies that each result value should be
used as is.
This parameter is available with Postfix 2.2 and later.
@@ -203,15 +214,15 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
NOTE: DO NOT put quotes around the result format!
domain (default: no domain list)
- This is a list of domain names, paths to files, or "type:table"
+ This is a list of domain names, paths to files, or "type:table"
databases. When specified, only fully qualified search keys with
- a *non-empty* localpart and a matching domain are eligible for
+ a *non-empty* localpart and a matching domain are eligible for
lookup: 'user' lookups, bare domain lookups and "@domain"
- lookups are not performed. This can significantly reduce the
+ lookups are not performed. This can significantly reduce the
query load on the PostgreSQL server.
domain = postfix.org, hash:/etc/postfix/searchdomains
- It is best not to use SQL to store the domains eligible for SQL
+ It is best not to use SQL to store the domains eligible for SQL
lookups.
This parameter is available with Postfix 2.2 and later.
@@ -220,28 +231,28 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
the input keys are always unqualified.
expansion_limit (default: 0)
- A limit on the total number of result elements returned (as a
+ A limit on the total number of result elements returned (as a
comma separated list) by a lookup against the map. A setting of
- zero disables the limit. Lookups fail with a temporary error if
- the limit is exceeded. Setting the limit to 1 ensures that
+ zero disables the limit. Lookups fail with a temporary error if
+ the limit is exceeded. Setting the limit to 1 ensures that
lookups do not return multiple values.
OBSOLETE MAIN.CF PARAMETERS
- For compatibility with other Postfix lookup tables, PostgreSQL parame-
- ters can also be defined in main.cf. In order to do that, specify as
+ For compatibility with other Postfix lookup tables, PostgreSQL parame-
+ ters can also be defined in main.cf. In order to do that, specify as
PostgreSQL source a name that doesn't begin with a slash or a dot. The
- PostgreSQL parameters will then be accessible as the name you've given
+ PostgreSQL parameters will then be accessible as the name you've given
the source in its definition, an underscore, and the name of the param-
- eter. For example, if the map is specified as "pgsql:pgsqlname", the
+ eter. For example, if the map is specified as "pgsql:pgsqlname", the
parameter "hosts" would be defined in main.cf as "pgsqlname_hosts".
- Note: with this form, the passwords for the PostgreSQL sources are
+ Note: with this form, the passwords for the PostgreSQL sources are
written in main.cf, which is normally world-readable. Support for this
form will be removed in a future Postfix version.
OBSOLETE QUERY INTERFACES
This section describes query interfaces that are deprecated as of Post-
- fix 2.2. Please migrate to the new query interface as the old inter-
+ fix 2.2. Please migrate to the new query interface as the old inter-
faces are slated to be phased out.
select_function
@@ -251,14 +262,14 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
This is equivalent to:
query = SELECT my_lookup_user_alias('%s')
- This parameter overrides the legacy table-related fields
- (described below). With Postfix versions prior to 2.2, it also
- overrides the query parameter. Starting with Postfix 2.2, the
- query parameter has highest precedence, and the select_function
+ This parameter overrides the legacy table-related fields
+ (described below). With Postfix versions prior to 2.2, it also
+ overrides the query parameter. Starting with Postfix 2.2, the
+ query parameter has highest precedence, and the select_function
parameter is deprecated.
- The following parameters (with lower precedence than the select_func-
- tion interface described above) can be used to build the SQL select
+ The following parameters (with lower precedence than the select_func-
+ tion interface described above) can be used to build the SQL select
statement as follows:
SELECT [select_field]
@@ -266,13 +277,13 @@ PGSQL_TABLE(5) PGSQL_TABLE(5)
WHERE [where_field] = '%s'
[additional_conditions]
- The specifier %s is replaced with each lookup by the lookup key and is
- escaped so if it contains single quotes or other odd characters, it
+ The specifier %s is replaced with each lookup by the lookup key and is
+ escaped so if it contains single quotes or other odd characters, it
will not cause a parse error, or worse, a security problem.
Starting with Postfix 2.2, this interface is obsoleted by the more gen-
eral query interface described above. If higher precedence the query or
- select_function parameters described above are defined, the parameters
+ select_function parameters described above are defined, the parameters
described here are ignored.
select_field
diff --git a/postfix/man/man5/pgsql_table.5 b/postfix/man/man5/pgsql_table.5
index 6d9df55fd..1d0064cf6 100644
--- a/postfix/man/man5/pgsql_table.5
+++ b/postfix/man/man5/pgsql_table.5
@@ -55,7 +55,7 @@ return the key itself or a constant value.
.fi
.IP "\fBhosts\fR"
The hosts that Postfix will try to connect to and query
-from. Besides a \fBpostgresql://\fR connection URI, this
+from. Besides a PostgreSQL connection URI, this
setting supports the historical forms \fBunix:/\fIpathname\fR
for UNIX\-domain sockets and \fBinet:\fIhost:port\fR for TCP
connections, where the \fBunix:\fR and \fBinet:\fR prefixes
@@ -63,18 +63,28 @@ are accepted and ignored for backwards compatibility.
Examples:
.nf
hosts = postgresql://username@example.com/\fIdatabasename\fR?sslmode=require
+ hosts = postgres://user:secret@localhost
hosts = inet:host1.some.domain inet:host2.some.domain:port
hosts = host1.some.domain host2.some.domain:port
hosts = unix:/file/name
.fi
+See https://www.postgresql.org/docs/current/libpq\-connect.html
+for the supported connection URI syntax.
+
The hosts are tried in random order. The connections are
automatically closed after being idle for about 1 minute,
-and are re\-opened as necessary.
+and are re\-opened as necessary. See \fBidle_interval\fR
+for details.
-NOTE: if "hosts" specifies one load balancer and no alternative
+NOTE: if \fBhosts\fR specifies a PostgreSQL connection URI,
+the PostgreSQL client library will ignore the \fBdbname\fR
+setting for that connection.
+
+NOTE: if \fBhosts\fR specifies one load balancer and no
+alternative
servers, specify the load balancer multiple times in the
-"hosts" line. Without the duplicate info, the Postfix
+\fBhosts\fR line. Without the duplicate info, the Postfix
PostgreSQL client would not reconnect immediately to the
same load balancer after a PostgreSQL server failure.
.IP "\fBuser\fR"
@@ -85,14 +95,18 @@ Example:
user = someone
password = some_password
.fi
-.IP "\fBdbname\fR (required)"
+.IP "\fBdbname\fR"
The database name on the servers. Example:
.nf
dbname = customer_database
.fi
.sp
-This setting is required, but ignored when a postgresql://
-URI specifies a database name.
+The \fBdbname\fR setting is ignored for \fBhosts\fR connections
+that are specified as an URI.
+
+The \fBdbname\fR setting is required with Postfix 3.10 and later,
+when \fBhosts\fR specifies any non\-URI connection; it is always
+required with earlier Postfix versions.
.IP "\fBencoding\fR"
The encoding used by the database client. The default setting
is:
diff --git a/postfix/proto/pgsql_table b/postfix/proto/pgsql_table
index 6bc797b5b..e434779a3 100644
--- a/postfix/proto/pgsql_table
+++ b/postfix/proto/pgsql_table
@@ -45,7 +45,7 @@
# .fi
# .IP "\fBhosts\fR"
# The hosts that Postfix will try to connect to and query
-# from. Besides a \fBpostgresql://\fR connection URI, this
+# from. Besides a PostgreSQL connection URI, this
# setting supports the historical forms \fBunix:/\fIpathname\fR
# for UNIX-domain sockets and \fBinet:\fIhost:port\fR for TCP
# connections, where the \fBunix:\fR and \fBinet:\fR prefixes
@@ -53,18 +53,28 @@
# Examples:
# .nf
# hosts = postgresql://username@example.com/\fIdatabasename\fR?sslmode=require
+# hosts = postgres://user:secret@localhost
# hosts = inet:host1.some.domain inet:host2.some.domain:port
# hosts = host1.some.domain host2.some.domain:port
# hosts = unix:/file/name
# .fi
#
+# See https://www.postgresql.org/docs/current/libpq-connect.html
+# for the supported connection URI syntax.
+#
# The hosts are tried in random order. The connections are
# automatically closed after being idle for about 1 minute,
-# and are re-opened as necessary.
+# and are re-opened as necessary. See \fBidle_interval\fR
+# for details.
#
-# NOTE: if "hosts" specifies one load balancer and no alternative
+# NOTE: if \fBhosts\fR specifies a PostgreSQL connection URI,
+# the PostgreSQL client library will ignore the \fBdbname\fR
+# setting for that connection.
+#
+# NOTE: if \fBhosts\fR specifies one load balancer and no
+# alternative
# servers, specify the load balancer multiple times in the
-# "hosts" line. Without the duplicate info, the Postfix
+# \fBhosts\fR line. Without the duplicate info, the Postfix
# PostgreSQL client would not reconnect immediately to the
# same load balancer after a PostgreSQL server failure.
# .IP "\fBuser\fR"
@@ -75,14 +85,18 @@
# user = someone
# password = some_password
# .fi
-# .IP "\fBdbname\fR (required)"
+# .IP "\fBdbname\fR"
# The database name on the servers. Example:
# .nf
# dbname = customer_database
# .fi
# .sp
-# This setting is required, but ignored when a postgresql://
-# URI specifies a database name.
+# The \fBdbname\fR setting is ignored for \fBhosts\fR connections
+# that are specified as an URI.
+#
+# The \fBdbname\fR setting is required with Postfix 3.10 and later,
+# when \fBhosts\fR specifies any non-URI connection; it is always
+# required with earlier Postfix versions.
# .IP "\fBencoding\fR"
# The encoding used by the database client. The default setting
# is:
diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in
index 491fe0f15..e1410bb7e 100644
--- a/postfix/src/global/Makefile.in
+++ b/postfix/src/global/Makefile.in
@@ -1252,6 +1252,7 @@ dict_pgsql.o: ../../include/myrand.h
dict_pgsql.o: ../../include/split_at.h
dict_pgsql.o: ../../include/stringops.h
dict_pgsql.o: ../../include/sys_defs.h
+dict_pgsql.o: ../../include/valid_uri_scheme.h
dict_pgsql.o: ../../include/vbuf.h
dict_pgsql.o: ../../include/vstream.h
dict_pgsql.o: ../../include/vstring.h
diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c
index c62685451..53fafd228 100644
--- a/postfix/src/global/dict_pgsql.c
+++ b/postfix/src/global/dict_pgsql.c
@@ -92,6 +92,7 @@
#include "myrand.h"
#include "events.h"
#include "stringops.h"
+#include "valid_uri_scheme.h"
/* Global library. */
@@ -127,6 +128,7 @@ typedef struct {
typedef struct {
int len_hosts; /* number of hosts */
HOST **db_hosts; /* hosts on which databases reside */
+ char *non_uri_target; /* require dbname to be specified */
} PLPGSQL;
typedef struct {
@@ -626,7 +628,7 @@ static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
dict_pgsql->username = cfg_get_str(p, "user", "", 0, 0);
dict_pgsql->password = cfg_get_str(p, "password", "", 0, 0);
- dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 1, 0);
+ dict_pgsql->dbname = cfg_get_str(p, "dbname", "", 0, 0);
dict_pgsql->encoding = cfg_get_str(p, "encoding", "UTF8", 1, 0);
dict_pgsql->retry_interval = cfg_get_int(p, "retry_interval",
DEF_RETRY_INTV, 1, 0);
@@ -723,6 +725,21 @@ DICT *dict_pgsql_open(const char *name, int open_flags, int dict_flags)
dict_pgsql->pldb = plpgsql_init(dict_pgsql->hosts);
if (dict_pgsql->pldb == NULL)
msg_fatal("couldn't initialize pldb!\n");
+ if (msg_verbose && dict_pgsql->pldb->non_uri_target == 0
+ && dict_pgsql->dbname[0] != 0)
+ msg_info("%s:%s table ignores 'dbname' field -- "
+ "all 'hosts' targets are URIs",
+ DICT_TYPE_PGSQL, name);
+ if (dict_pgsql->pldb->non_uri_target && dict_pgsql->dbname[0] == 0) {
+ DICT *ret;
+
+ ret == (dict_surrogate(DICT_TYPE_PGSQL, name, open_flags, dict_flags,
+ "%s:%s host target '%s' requires dbname setting",
+ DICT_TYPE_PGSQL, name,
+ dict_pgsql->pldb->non_uri_target));
+ dict_pgsql_close(&dict_pgsql->dict);
+ return (ret);
+ }
dict_pgsql->dict.owner = cfg_get_owner(dict_pgsql->parser);
return (DICT_DEBUG (&dict_pgsql->dict));
}
@@ -737,8 +754,12 @@ static PLPGSQL *plpgsql_init(ARGV *hosts)
PLDB = (PLPGSQL *) mymalloc(sizeof(PLPGSQL));
PLDB->len_hosts = hosts->argc;
PLDB->db_hosts = (HOST **) mymalloc(sizeof(HOST *) * hosts->argc);
- for (i = 0; i < hosts->argc; i++)
+ PLDB->non_uri_target = 0;
+ for (i = 0; i < hosts->argc; i++) {
PLDB->db_hosts[i] = host_init(hosts->argv[i]);
+ if (PLDB->db_hosts[i]->type != TYPECONNSTR)
+ PLDB->non_uri_target = PLDB->db_hosts[i]->name;
+ }
return PLDB;
}
@@ -758,9 +779,9 @@ static HOST *host_init(const char *hostname)
host->ts = 0;
/*
- * Modern syntax: "postgresql://connection-info".
+ * Modern syntax: connection URI.
*/
- if (strncmp(d, "postgresql:", 11) == 0) {
+ if (valid_uri_scheme(d)) {
host->type = TYPECONNSTR;
host->name = mystrdup(d);
host->port = 0;
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 02a818a0f..eb01b3a57 100644
--- a/postfix/src/global/mail_version.h
+++ b/postfix/src/global/mail_version.h
@@ -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 "20241024"
+#define MAIL_RELEASE_DATE "20241025"
#define MAIL_VERSION_NUMBER "3.10"
#ifdef SNAPSHOT
diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in
index 531b786bf..77781bc0a 100644
--- a/postfix/src/util/Makefile.in
+++ b/postfix/src/util/Makefile.in
@@ -46,7 +46,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
sane_strtol.c hash_fnv.c ldseed.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c \
mkmap_fail.c mkmap_lmdb.c mkmap_open.c mkmap_sdbm.c inet_prefix_top.c \
inet_addr_sizes.c quote_for_json.c mystrerror.c \
- sane_sockaddr_to_hostaddr.c normalize_ws.c
+ sane_sockaddr_to_hostaddr.c normalize_ws.c valid_uri_scheme.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
@@ -94,7 +94,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
sane_strtol.o hash_fnv.o ldseed.o mkmap_db.o mkmap_dbm.o \
mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.o \
quote_for_json.o mystrerror.o sane_sockaddr_to_hostaddr.o \
- normalize_ws.o
+ normalize_ws.o valid_uri_scheme.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
@@ -126,7 +126,7 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \
check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \
known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h mkmap.h \
- inet_prefix_top.h inet_addr_sizes.h
+ inet_prefix_top.h inet_addr_sizes.h valid_uri_scheme.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
DEFS = -I. -D$(SYSTYPE)
@@ -149,7 +149,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
vbuf_print split_qnameval vstream msg_logger byte_mask \
known_tcp_ports dict_stream find_inet binhash hash_fnv argv \
clean_env inet_prefix_top printable readlline quote_for_json \
- normalize_ws
+ normalize_ws valid_uri_scheme
PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
$(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(LIB_SUFFIX)
HTABLE_FIX = NORANDOMIZE=1
@@ -618,6 +618,11 @@ normalize_ws: $(LIB)
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+valid_uri_scheme: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
hex_quote_test ctable_test inet_addr_list_test base64_code_test \
attr_scan64_test attr_scan0_test host_port_test dict_tests \
@@ -629,7 +634,7 @@ tests: all valid_hostname_test mac_expand_test dict_test unescape_test \
vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
binhash_test argv_test inet_prefix_top_test printable_test \
valid_utf8_string_test readlline_test quote_for_json_test \
- normalize_ws_test
+ normalize_ws_test valid_uri_scheme_test
dict_tests: all dict_test \
dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
@@ -1109,6 +1114,9 @@ quote_for_json_test: quote_for_json
normalize_ws_test: normalize_ws
$(SHLIB_ENV) ${VALGRIND} ./normalize_ws
+valid_uri_scheme_test: valid_uri_scheme
+ $(SHLIB_ENV) ${VALGRIND} ./valid_uri_scheme
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
@@ -2893,6 +2901,16 @@ valid_hostname.o: valid_hostname.c
valid_hostname.o: valid_hostname.h
valid_hostname.o: vbuf.h
valid_hostname.o: vstring.h
+valid_uri_scheme.o: check_arg.h
+valid_uri_scheme.o: msg.h
+valid_uri_scheme.o: msg_vstream.h
+valid_uri_scheme.o: stringops.h
+valid_uri_scheme.o: sys_defs.h
+valid_uri_scheme.o: valid_uri_scheme.c
+valid_uri_scheme.o: valid_uri_scheme.h
+valid_uri_scheme.o: vbuf.h
+valid_uri_scheme.o: vstream.h
+valid_uri_scheme.o: vstring.h
valid_utf8_hostname.o: check_arg.h
valid_utf8_hostname.o: midna_domain.h
valid_utf8_hostname.o: msg.h
diff --git a/postfix/src/util/valid_uri_scheme.c b/postfix/src/util/valid_uri_scheme.c
new file mode 100644
index 000000000..6a10f225b
--- /dev/null
+++ b/postfix/src/util/valid_uri_scheme.c
@@ -0,0 +1,129 @@
+/*++
+/* NAME
+/* valid_uri_scheme 3
+/* SUMMARY
+/* validate scheme:// prefix
+/* SYNOPSIS
+/* #include
+/*
+/* int valid_uri_scheme(const char *str)
+/* DESCRIPTION
+/* valid_uri_scheme() takes a null-terminated string and returns
+/* the length of a valid scheme:// prefix, or zero if no valid
+/* prefix was found.
+/*
+/* This function requires that input is encoded in ASCII or UTF-8.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+ /*
+ * System library.
+ */
+#include
+#include
+#include
+
+ /*
+ * Utility library.
+ */
+#include
+#include
+#include
+#include
+
+/* valid_uri_scheme - predicate that string starts with scheme:// */
+
+ssize_t valid_uri_scheme(const char *str)
+{
+ const char *cp = str;
+ int ch = *cp++;
+
+ /* Per RFC 3986, a valid scheme starts with ALPHA. */
+ if (!ISALPHA(ch))
+ return (0);
+
+ while ((ch = *cp++) != 0) {
+ /* A valid scheme continues with ALPHA | DIGIT | '+' | '-'. */
+ if (ISALNUM(ch) || ch == '+' || ch == '-')
+ continue;
+ /* A valid scheme is followed by "://". */
+ if (ch == ':' && *cp++ == '/' && *cp++ == '/')
+ return (cp - str);
+ /* Not a valid scheme. */
+ break;
+ }
+ /* Not a valid scheme. */
+ return (0);
+}
+
+#ifdef TEST
+
+typedef struct TEST_CASE {
+ const char *label;
+ const char *input;
+ const ssize_t want;
+} TEST_CASE;
+
+#define PASS (0)
+#define FAIL (1)
+
+static const TEST_CASE test_cases[] = {
+ {"accepts_alpha_scheme", "abcd://blah", sizeof("abcd://") - 1},
+ {"accepts_mixed_scheme", "a-bcd+123://blah", sizeof("a-bcd+123://") - 1},
+ {"rejects_minus_first", "-bcd+123://blah'", 0},
+ {"rejects_plus_first", "+123://blah", 0},
+ {"rejects_digit_first", "123://blah", 0},
+ {"rejects_other_first", "?123://blah", 0},
+ {"rejects_other_middle", "abcd?123://blah", 0},
+ {"rejects_other_end", "abcd-123?://blah", 0},
+ {"rejects_non_scheme", "inet:host:port", 0},
+ {"rejects_no_colon", "inet", 0},
+ {"rejects_colon_slash", "abcd:/blah", 0},
+ {"rejects_empty", "", 0},
+ {0,}
+};
+
+static int test_validate_scheme(const TEST_CASE *tp)
+{
+ int got;
+
+ got = valid_uri_scheme(tp->input);
+ if (got != tp->want) {
+ msg_warn("got '%ld', want '%ld'", (long) got, (long) tp->want);
+ return (FAIL);
+ }
+ return (PASS);
+}
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ int test_failed;
+
+ msg_info("RUN %s", tp->label);
+ test_failed = test_validate_scheme(tp);
+ if (test_failed) {
+ msg_info("FAIL %s", tp->label);
+ fail++;
+ } else {
+ msg_info("PASS %s", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/postfix/src/util/valid_uri_scheme.h b/postfix/src/util/valid_uri_scheme.h
new file mode 100644
index 000000000..94327ed30
--- /dev/null
+++ b/postfix/src/util/valid_uri_scheme.h
@@ -0,0 +1,28 @@
+#ifndef _VALID_SCHEME_H_INCLUDED_
+#define _VALID_SCHEME_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_uri_scheme 3h
+/* SUMMARY
+/* validate scheme:// prefix
+/* SYNOPSIS
+/* #include
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern ssize_t valid_uri_scheme(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* porcupine.org
+/*--*/
+
+#endif