diff -u --recursive --new-file snapshot-20000531-vanilla/PG_README snapshot-20000531/PG_README --- snapshot-20000531-vanilla/PG_README Thu Jan 1 01:00:00 1970 +++ snapshot-20000531/PG_README Wed Aug 9 15:02:41 2000 @@ -0,0 +1,88 @@ +[Code contributed by Mathieu Arnold] + +We've written code to add a Pg map type. It utilizes the Pg +client library, which can be obtained from: + + http://www.postgresql.org/ + +In order to build postfix with Pg map support, you will need to add +-DHAS_PG and -I for the directory containing the postgresql headers, and +the libpq library (and libcrypt) to AUXLIBS, for example: + +make -f Makefile.init makefiles \ + 'CCARGS=-DHAS_PG -I/some/where/include/postgresql' \ + 'AUXLIBS=/some/where/lib/postgresql/libpq.a -lcrypt' + +then, just run 'make'. + +Postfix installations which may benefit from using Pg map types +include sites that have a need for instantaneous updates of +forwarding, and sites that may benefit from having mail exchangers +reference a networked database, possibly working in conjunction with a +customer database of sorts. + +Once postfix is built with Pg support, you can specify a map type +in main.cf like this: + +alias_maps = Pg:/etc/postfix/Pg-aliases.cf + +The file /etc/postfix/Pg-aliases.cf specifies lots of information +telling postfix how to reference the postgresql database. An example +postgresql map config file follows: + +# +# postgresql config file for alias lookups on postfix +# comments are ok. +# + +# the user name and password to log into the Pg server +user = someone +password = some_passwordd + +# the database name on the servers +dbname = customer_database + +# the table name +table = mxaliases + +# +select_field = forw_addr +where_field = alias + +# you may specify additional_conditions here +additional_conditions = and status = 'paid' + +# the above variables will result in a query of +# the form: +# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid' +# ($lookup is escaped so if it contains single quotes or other odd +# characters, it will not cause a parse error in the sql). +# +# the hosts that postfix will try to connect to +# and query from (in the order listed) +hosts = host1.some.domain host2.some.domain + +# end postgresql config file + +Some notes: + +This configuration interface setup allows for multiple postgresql +databases: you can use one for a virtual table, one for an access +table, and one for an aliases table if you want. + +Since sites that have a need for multiple mail exchangers may enjoy +the convenience of using a networked mailer database, but do not want +to introduce a single point of failure to their system, we've included +the ability to have postfix reference multiple hosts for access to a +single Pg map. This will work if sites set up mirrored Pg +databases on two or more hosts. Whenever queries fail with an error +at one host, the rest of the hosts will be tried in order. Each host +that is in an error state will undergo a reconnection attempt every so +often, and if no Pg server hosts are reachable, then mail will be +deferred until atleast one of those hosts is reachable. + +Performance of postfix with Pg has not been thoroughly tested, +however, we have found it to be stable. Busy mail servers using Pg +maps will generate lots of concurrent Pg clients, so the Pg +server(s) should be run with this fact in mind. Any further +performance information, in addition to any feedback is most welcome. diff -u --recursive --new-file snapshot-20000531-vanilla/util/Makefile.in snapshot-20000531/util/Makefile.in --- snapshot-20000531-vanilla/util/Makefile.in Sun May 7 02:52:16 2000 +++ snapshot-20000531/util/Makefile.in Wed Aug 9 15:02:41 2000 @@ -1,7 +1,7 @@ SHELL = /bin/sh SRCS = argv.c argv_split.c attr.c basename.c binhash.c chroot_uid.c \ close_on_exec.c concatenate.c dict.c dict_db.c dict_dbm.c \ - dict_env.c dict_ht.c dict_ldap.c dict_mysql.c dict_ni.c dict_nis.c \ + dict_env.c dict_ht.c dict_ldap.c dict_mysql.c dict_Pg.c dict_ni.c dict_nis.c \ dict_nisplus.c dict_open.c dir_forest.c doze.c environ.c \ events.c exec_command.c fifo_listen.c fifo_trigger.c file_limit.c \ find_inet.c fsspace.c fullname.c get_domainname.c get_hostname.c \ @@ -24,7 +24,7 @@ sane_link.c unescape.c timed_read.c timed_write.c OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \ close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \ - dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \ + dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_Pg.o dict_ni.o dict_nis.o \ dict_nisplus.o dict_open.o dir_forest.o doze.o environ.o \ events.o exec_command.o fifo_listen.o fifo_trigger.o file_limit.o \ find_inet.o fsspace.o fullname.o get_domainname.o get_hostname.o \ @@ -46,7 +46,7 @@ clean_env.o watchdog.o spawn_command.o duplex_pipe.o sane_rename.o \ sane_link.o unescape.o timed_read.o timed_write.o HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \ - dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \ + dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h dict_Pg.h \ dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \ exec_command.h find_inet.h fsspace.h fullname.h get_domainname.h \ get_hostname.h htable.h inet_addr_host.h inet_addr_list.h \ @@ -389,6 +389,8 @@ dict_ldap.o: sys_defs.h dict_mysql.o: dict_mysql.c dict_mysql.o: sys_defs.h +dict_Pg.o: dict_Pg.c +dict_Pg.o: sys_defs.h dict_ni.o: dict_ni.c dict_ni.o: sys_defs.h dict_nis.o: dict_nis.c @@ -428,6 +430,7 @@ dict_open.o: dict_ni.h dict_open.o: dict_ldap.h dict_open.o: dict_mysql.h +dict_open.o: dict_Pg.h dict_open.o: dict_pcre.h dict_open.o: dict_regexp.h dict_open.o: stringops.h diff -u --recursive --new-file snapshot-20000531-vanilla/util/dict_Pg.c snapshot-20000531/util/dict_Pg.c --- snapshot-20000531-vanilla/util/dict_Pg.c Thu Jan 1 01:00:00 1970 +++ snapshot-20000531/util/dict_Pg.c Wed Aug 9 15:10:29 2000 @@ -0,0 +1,469 @@ +/*++ +/* NAME +/* dict_Pg 3 +/* SUMMARY +/* dictionary manager interface to db files based on dict_mysql +/* SYNOPSIS +/* #include +/* #include +/* +/* DICT *dict_Pg_open(name, dummy, unused_dict_flags) +/* const char *name; +/* int dummy; +/* int unused_dict_flags; +/* DESCRIPTION +/* dict_Pg_open() opens the Pg databases with name dbname on +/* each host in hostlist and registers under the given name with the +/* dictionary manager. The result is a pointer to the installed dictionary, +/* or a null pointer in case of problems. +/* +/* Arguments: +/* .IP name +/* The path of the Pg configuration file. The file encodes a number of +/* pieces of information: username, password, databasename, table, +/* select_field, where_field, and hosts. For example, if you want the map to +/* reference databases of the name "your_db" and execute a query like this: +/* select forw_addr from aliases where alias like '' against +/* any database called "vmailer_info" located on hosts host1.some.domain and +/* host2.some.domain, logging in as user "vmailer" and password "passwd" then +/* the configuration file should read: +/* +/* user = vmailer +/* password = passwd +/* DBname = vmailer_info +/* table = aliases +/* select_field = forw_addr +/* where_field = alias +/* hosts = host1.some.domain host2.some.domain +/* +/* .IP other_name +/* reference for outside use. +/* .IP unusued_flags +/* unused flags +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Mathieu Arnold +/* arn_mat@club-internet.fr +/*--*/ + +/* System library. */ +#include "sys_defs.h" + +#ifdef HAS_PG +#include +#include +#include +#include +#include + +/* Utility library. */ +#include "dict.h" +#include "msg.h" +#include "mymalloc.h" +#include "dict_Pg.h" +#include "argv.h" +#include "vstring.h" + +extern int dict_errno; + +typedef struct { + char *username; + char *password; + char *dbname; + char *table; + char *select_field; + char *where_field; + char *additional_conditions; + char **hostnames; + int len_hosts; +} PG_NAME; + +typedef struct { + DICT dict; + PLPG *pldb; + PG_NAME *name; +} DICT_PG; + +/* Pgname_parse - parse Pg configuration file */ + +static PG_NAME *Pgname_parse(const char *Pgcf_path) +{ + int i; + char *nameval; + char *hosts; + PG_NAME *name = (PG_NAME *) mymalloc(sizeof(PG_NAME)); + ARGV *hosts_argv; + + dict_load_file("Pg_options", Pgcf_path); + /* Pg username lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "user")) == NULL) + name->username = mystrdup(""); + else + name->username = mystrdup(nameval); + if (msg_verbose) + msg_info("dict_Pg_parse: set username to '%s'", name->username); + /* password lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "password")) == NULL) + name->password = mystrdup(""); + else + name->password = mystrdup(nameval); + if (msg_verbose) + msg_info("dict_Pg_parse: set password to '%s'", name->password); + + /* database name lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "dbname")) == NULL) + msg_fatal("%s: Pg options file does not include database name", Pgcf_path); + else + name->dbname = mystrdup(nameval); + if (msg_verbose) + msg_info("Pg_name_parse: set database name to '%s'", name->dbname); + + /* table lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "table")) == NULL) + msg_fatal("%s: Pg options file does not include table name", Pgcf_path); + else + name->table = mystrdup(nameval); + if (msg_verbose) + msg_info("Pg_name_parse: set table name to '%s'", name->table); + + /* select field lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "select_field")) == NULL) + msg_fatal("%s: Pg options file does not include select field", Pgcf_path); + else + name->select_field = mystrdup(nameval); + if (msg_verbose) + msg_info("Pg_name_parse: set select_field to '%s'", name->select_field); + + /* where field lookup */ + if ((nameval = (char *) dict_lookup("Pg_options", "where_field")) == NULL) + msg_fatal("%s: Pg options file does not include where field", Pgcf_path); + else + name->where_field = mystrdup(nameval); + if (msg_verbose) + msg_info("Pg_name_parse: set where_field to '%s'", name->where_field); + + /* additional conditions */ + if ((nameval = (char *) dict_lookup("Pg_options", "additional_conditions")) == NULL) + name->additional_conditions = mystrdup(""); + else + name->additional_conditions = mystrdup(nameval); + if (msg_verbose) + msg_info("Pg_name_parse: set additional_conditions to '%s'", name->additional_conditions); + + /* Pg server hosts */ + if ((nameval = (char *) dict_lookup("Pg_options", "hosts")) == NULL) + hosts = mystrdup(""); + else + hosts = mystrdup(nameval); + /* coo argv interface */ + hosts_argv = argv_split(hosts, " "); + argv_terminate(hosts_argv); + + if (hosts_argv->argc == 0) { /* no hosts specified, + * default to 'localhost' */ + msg_info("Pg_name_parse: no hostnames specified, defaulting to 'localhost'"); + name->len_hosts = 1; + name->hostnames = (char **) mymalloc(sizeof(char *)); + name->hostnames[0] = mystrdup("localhost"); + } else { + name->len_hosts = hosts_argv->argc; + name->hostnames = (char **) mymalloc((sizeof(char *)) * name->len_hosts); + i = 0; + for (i = 0; hosts_argv->argv[i] != NULL; i++) { + name->hostnames[i] = mystrdup(hosts_argv->argv[i]); + if (msg_verbose) + msg_info("adding host '%s' to list of Pg server hosts", name->hostnames[i]); + } + } + myfree(hosts); + argv_free(hosts_argv); + return name; +} + +/* dict_Pg_lookup - find database entry return 0 if no alias found */ +static const char *dict_Pg_lookup(DICT *dict, const char *name) +{ + PGresult *query_res; + char * row; + + int i, + numrows; + static VSTRING *result; + static VSTRING *query = 0; + char *name_escaped = 0; + DICT_PG *dict_Pg; + PLPG *pldb; + + dict_Pg = (DICT_PG *) dict; + pldb = dict_Pg->pldb; + + /* initialization for query */ + query = vstring_alloc(24); + vstring_strcpy(query, ""); + + if ((name_escaped = (char *) mymalloc((sizeof(char) * (strlen(name) * 2) +1))) == NULL) { + msg_fatal("dict_Pg_lookup: out of memory."); + } + + /* prepare the query */ + Pg_escape_string(name_escaped, name, (unsigned int) strlen(name)); + vstring_sprintf(query, "select %s from %s where %s = '%s' %s", dict_Pg->name->select_field, + dict_Pg->name->table, dict_Pg->name->where_field, name_escaped, + dict_Pg->name->additional_conditions); + if (msg_verbose) + msg_info("dict_Pg_lookup using sql query: %s", vstring_str(query)); + /* free mem associated with preparing the query */ + myfree(name_escaped); + /* do the query */ + + if ((query_res = plPg_query(pldb, vstring_str(query))) == NULL) { + dict_errno = DICT_ERR_RETRY; + vstring_free(query); + return 0; + } + dict_errno = 0; + /* free the vstring query */ + vstring_free(query); + numrows = PQntuples(query_res); + if (msg_verbose) + msg_info("dict_Pg_lookup: retrieved %d rows", numrows); + if (numrows == 0) { + PQclear(query_res); + return 0; + } + if (result == 0) + result = vstring_alloc(10); + vstring_strcpy(result, ""); + for (i = 0; i < numrows; i++) { + row = PQgetvalue(query_res,i,0); + if (msg_verbose > 1) + msg_info("dict_Pg_lookup: retrieved row: %d: %s", i, row); + if (i > 0) + vstring_strcat(result, ","); + vstring_strcat(result, row); + } + PQclear(query_res); + return vstring_str(result); +} + +/* dict_Pg_close - unregister, disassociate from database */ +static void dict_Pg_close(DICT *dict) +{ + int i; + DICT_PG *dict_Pg = (DICT_PG *) dict; + + plPg_dealloc(dict_Pg->pldb); + myfree(dict_Pg->name->username); + myfree(dict_Pg->name->password); + myfree(dict_Pg->name->dbname); + myfree(dict_Pg->name->table); + myfree(dict_Pg->name->select_field); + myfree(dict_Pg->name->where_field); + myfree(dict_Pg->name->additional_conditions); + for (i = 0; i < dict_Pg->name->len_hosts; i++) { + myfree(dict_Pg->name->hostnames[i]); + } + myfree((char *) dict_Pg->name); +} + +/* dict_Pg_update - add or update table entry */ +static void dict_Pg_update(DICT *dict, const char *unused_name, const char *unused_value) +{ + DICT_PG *dict_Pg = (DICT_PG *) dict; + + msg_fatal("dict_Pg_update: attempt to update Pg database"); +} + +/* dict_Pg_open - create association with database */ +DICT *dict_Pg_open(const char *name, int unused_flags, int unused_dict_flags) +{ + DICT_PG *dict_Pg; + int connections; + + dict_Pg = (DICT_PG *) mymalloc(sizeof(DICT_PG)); + dict_Pg->dict.lookup = dict_Pg_lookup; + dict_Pg->dict.update = dict_Pg_update; + dict_Pg->dict.close = dict_Pg_close; + dict_Pg->dict.fd = -1; /* there's no file descriptor + * for locking */ + dict_Pg->name = (PG_NAME *) mymalloc(sizeof(PG_NAME)); + dict_Pg->name = Pgname_parse(name); + dict_Pg->pldb = plPg_init(dict_Pg->name->dbname, + dict_Pg->name->hostnames, + dict_Pg->name->len_hosts); + if (dict_Pg->pldb == NULL) + msg_fatal("couldn't intialize pldb!\n"); + connections = plPg_connect(dict_Pg->pldb, dict_Pg->name->username, + dict_Pg->name->password); + if (connections == 0) + /* the Pg lookup mechanism will try to reconnect anyway ... */ + msg_warn("couldn't connect pldb to any database instances"); + else + msg_info("pldb connected to %d database instances", connections); + dict_register(name, (DICT *) dict_Pg); + return &dict_Pg->dict; +} + +/* host_init - initialize HOST structure */ +static HOST host_init(char *hostname) +{ + int stat; + PGconn * db; + time_t ts; + HOST host; + + host.stat = STATUNTRIED; + host.hostname = hostname; + host.db = db; + host.ts = ts; + return host; +} + +/* + * plPg_init - initalize a PG database. + * Return NULL on failure, or a PLPG * on success. + */ +PLPG *plPg_init(char *dbname, + char *hostnames[], + int len_hosts) +{ + PLPG *PLDB; + PGconn *dbs; + int i; + HOST host; + + if ((PLDB = (PLPG *) mymalloc(sizeof(PLPG))) == NULL) { + msg_fatal("mymalloc of pldb failed"); + } + PLDB->dbname = dbname; + PLDB->len_hosts = len_hosts; + if ((PLDB->db_hosts = (HOST *) mymalloc(sizeof(HOST) * len_hosts)) == NULL) + return NULL; + for (i = 0; i < len_hosts; i++) { + PLDB->db_hosts[i] = host_init(hostnames[i]); + } + return PLDB; +} + +/* plPg_dealloc - free memory associated with PLPG close databases */ +void plPg_dealloc(PLPG *PLDB) +{ + int i; + + for (i = 0; i < PLDB->len_hosts; i++) { + PQfinish(PLDB->db_hosts[i].db); + myfree(PLDB->db_hosts[i].hostname); + } + myfree((char *) PLDB->db_hosts); + myfree((char *) (PLDB)); +} + +/* plPg_down_host - down a HOST * */ +inline void plPg_down_host(HOST *host) +{ + if (host->stat != STATFAIL) + host->ts = time(&(host->ts)); + host->stat = STATFAIL; +} + +/* plPg_connect_single - + * used to reconnect to a single database when one is down and as a helper for + * plPg_connect + */ +int plPg_connect_single(PLPG *PLDB, int host) +{ + PLDB->db_hosts[host].db = PQsetdbLogin(PLDB->db_hosts[host].hostname,NULL,NULL,NULL,PLDB->dbname,PLDB->username,PLDB->password); + + if ( PLDB->db_hosts[host].db != NULL ) { + if (PQstatus(PLDB->db_hosts[host].db) == CONNECTION_OK) { + PLDB->db_hosts[host].stat = STATACTIVE; + return 1; + } else { + plPg_down_host(&(PLDB->db_hosts[host])); + msg_warn("%s", PQerrorMessage(PLDB->db_hosts[host].db)); + } + } else { + plPg_down_host(&(PLDB->db_hosts[host])); + msg_warn("%s", PQerrorMessage(PLDB->db_hosts[host].db)); + } + return 0; +} + +/* + * plPg_connect - + * given a PLPG struct PLDB *, connect it and select db. + * return the number of databases successfully connected (0 for failure) + */ +int plPg_connect(PLPG *PLDB, char *username, char *password) +{ + int i, + res; + + res = 0; + + PLDB->username = username; + PLDB->password = password; + + for (i = 0; i < PLDB->len_hosts; i++) { + res = res + plPg_connect_single(PLDB, i); + } + return res; +} + +/* plPg_ready_reconn - + given a downed HOST, return whether or not it should retry connection +*/ +int plPg_ready_reconn(HOST host) +{ + time_t t; + long now; + + now = (long) time(&t); + if ((now - ((long) host.ts)) >= RETRY_CONN_INTV) + return 1; + return 0; +} + +/* + * plPg_query - process a Pg query. Return 0 on success. + * On failure, log failure and try other db instances. + */ + +PGresult *plPg_query(PLPG *PLDB, const char *query) +{ + int i; + PGresult *res; + + for (i = 0; i < PLDB->len_hosts; i++) { + if ((PLDB->db_hosts[i].stat != STATACTIVE) && + (plPg_ready_reconn(PLDB->db_hosts[i]))) { + msg_warn("attempting to reconnect to host %s",PLDB->db_hosts[i].hostname); + plPg_connect_single(PLDB, i); + continue; + } + res=PQexec(PLDB->db_hosts[i].db,query); + if ( res && + ( PQresultStatus(res) == PGRES_COMMAND_OK || + PQresultStatus(res) == PGRES_TUPLES_OK)) { + return res; + } + msg_warn("%s", PQerrorMessage(PLDB->db_hosts[i].db)); + plPg_down_host(&(PLDB->db_hosts[i])); + } + return NULL; +} + +void Pg_escape_string(char * escaped, const char * name,int len) +{ + int i,j; + for (i=0,j=0;i<=len;i++,j++) { + if ((name[i] == '\'') || (name[i] == '\\')) { + escaped[j] = '\\'; + j++; + } + escaped[j] = name[i]; + } +} + +#endif diff -u --recursive --new-file snapshot-20000531-vanilla/util/dict_Pg.h snapshot-20000531/util/dict_Pg.h --- snapshot-20000531-vanilla/util/dict_Pg.h Thu Jan 1 01:00:00 1970 +++ snapshot-20000531/util/dict_Pg.h Wed Aug 9 15:10:42 2000 @@ -0,0 +1,48 @@ +#ifdef HAS_PG + +#include +#include "libpq-fe.h" + +#define STATACTIVE 0 +#define STATFAIL 1 +#define STATUNTRIED 2 +#define RETRY_CONN_INTV 300 /* 5 minutes */ + +extern DICT *dict_Pg_open(const char *name, int unused_flags, int dict_flags); + +typedef struct { + char *hostname; + int stat; /* STATUNTRIED | STATFAIL | STATCUR */ + time_t ts; /* used for attempting reconnection + * every so often if a host is down */ + PGconn *db; +} HOST; + + +typedef struct { + char *username; /* login for database */ + char *password; /* password for database */ + char *dbname; /* the name of the database on all + * the servers */ + HOST *db_hosts; /* the hosts on which the databases + * reside */ + int len_hosts; /* number of hosts */ +} PLPG; + +extern PLPG *plPg_init(char *dbname, char *hostnames[], int len_hosts); + +extern int plPg_connect(PLPG *PLDB, char *username, char *password); + +PGresult *plPg_query(PLPG *PLDB, const char *query); + +void plPg_dealloc(PLPG *PLDB); + +inline void plPg_down_host(HOST *host); + +int plPg_connect_single(PLPG *PLDB, int host); + +int plPg_ready_reconn(HOST host); + +void Pg_escape_string(char * escaped,const char * name,int len); + +#endif diff -u --recursive --new-file snapshot-20000531-vanilla/util/dict_open.c snapshot-20000531/util/dict_open.c --- snapshot-20000531-vanilla/util/dict_open.c Sat Mar 11 17:51:48 2000 +++ snapshot-20000531/util/dict_open.c Wed Aug 9 15:02:41 2000 @@ -163,6 +163,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,9 @@ #endif #ifdef HAS_MYSQL "mysql", dict_mysql_open, +#endif +#ifdef HAS_PG + "Pg", dict_Pg_open, #endif #ifdef HAS_PCRE "pcre", dict_pcre_open,