From a559c5da498625983050042ca8f3adac577b2b63 Mon Sep 17 00:00:00 2001 From: Wietse Z Venema Date: Fri, 25 Oct 2024 00:00:00 -0500 Subject: [PATCH] postfix-3.10-20241025 --- postfix/HISTORY | 8 ++ postfix/html/pgsql_table.5.html | 167 +++++++++++++++------------- postfix/man/man5/pgsql_table.5 | 28 +++-- postfix/proto/pgsql_table | 28 +++-- postfix/src/global/Makefile.in | 1 + postfix/src/global/dict_pgsql.c | 29 ++++- postfix/src/global/mail_version.h | 2 +- postfix/src/util/Makefile.in | 28 ++++- postfix/src/util/valid_uri_scheme.c | 129 +++++++++++++++++++++ postfix/src/util/valid_uri_scheme.h | 28 +++++ 10 files changed, 346 insertions(+), 102 deletions(-) create mode 100644 postfix/src/util/valid_uri_scheme.c create mode 100644 postfix/src/util/valid_uri_scheme.h 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