head 1.1; access; symbols; locks; strict; comment @# @; 1.1 date 2009.07.05.19.25.17; author rse; state Exp; branches; next ; commitid dBFtSCgjk0CdCyUt; desc @@ 1.1 log @add optional SQLite support @ text @Postfix SQLite Lookup Table Support http://www.treibsand.com/postfix-sqlite/ Index: src/global/Makefile.in --- src/global/Makefile.in.orig 2009-02-13 02:25:05 +0100 +++ src/global/Makefile.in 2009-07-05 21:16:55 +0200 @@@@ -4,7 +4,7 @@@@ clnt_stream.c conv_time.c db_common.c debug_peer.c debug_process.c \ defer.c deliver_completed.c deliver_flock.c deliver_pass.c \ deliver_request.c dict_ldap.c dict_mysql.c dict_pgsql.c \ - dict_proxy.c domain_list.c dot_lockfile.c dot_lockfile_as.c \ + dict_proxy.c dict_sqlite.c domain_list.c dot_lockfile.c dot_lockfile_as.c \ dsb_scan.c dsn.c dsn_buf.c dsn_mask.c dsn_print.c dsn_util.c \ ehlo_mask.c ext_prop.c file_id.c flush_clnt.c header_opts.c \ header_token.c input_transp.c int_filt.c is_header.c log_adhoc.c \ @@@@ -35,7 +35,7 @@@@ clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \ defer.o deliver_completed.o deliver_flock.o deliver_pass.o \ deliver_request.o dict_ldap.o dict_mysql.o dict_pgsql.o \ - dict_proxy.o domain_list.o dot_lockfile.o dot_lockfile_as.o \ + dict_proxy.o dict_sqlite.o domain_list.o dot_lockfile.o dot_lockfile_as.o \ dsb_scan.o dsn.o dsn_buf.o dsn_mask.o dsn_print.o dsn_util.o \ ehlo_mask.o ext_prop.o file_id.o flush_clnt.o header_opts.o \ header_token.o input_transp.o int_filt.o is_header.o log_adhoc.o \ @@@@ -65,7 +65,7 @@@@ canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \ - dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h domain_list.h \ + dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \ dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \ dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \ file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \ @@@@ -862,6 +862,13 @@@@ dict_proxy.o: dict_proxy.h dict_proxy.o: mail_params.h dict_proxy.o: mail_proto.h +dict_sqlite.o: ../../include/dict.h +dict_sqlite.o: ../../include/msg.h +dict_sqlite.o: ../../include/sys_defs.h +dict_sqlite.o: cfg_parser.h +dict_sqlite.o: db_common.h +dict_sqlite.o: dict_sqlite.c +dict_sqlite.o: dict_sqlite.h domain_list.o: ../../include/match_list.h domain_list.o: ../../include/match_ops.h domain_list.o: ../../include/sys_defs.h @@@@ -1233,6 +1240,7 @@@@ mail_dict.o: dict_mysql.h mail_dict.o: dict_pgsql.h mail_dict.o: dict_proxy.h +mail_dict.o: dict_sqlite.h mail_dict.o: mail_dict.c mail_dict.o: mail_dict.h mail_error.o: ../../include/name_mask.h Index: src/global/dict_sqlite.c --- /dev/null 2009-07-05 21:16:41 +0200 +++ src/global/dict_sqlite.c 2009-07-05 21:16:55 +0200 @@@@ -0,0 +1,278 @@@@ +/*++ +/* NAME +/* dict_sqlite 3 +/* SUMMARY +/* dictionary manager interface to SQLite3 databases +/* SYNOPSIS +/* #include +/* +/* DICT *dict_sqlite_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sqlite_open() creates a dictionary of type 'sqlite'. This +/* dictionary is an interface for the postfix key->value mappings +/* to SQLite. The result is a pointer to the installed dictionary, +/* or a null pointer in case of problems. +/* .PP +/* Arguments: +/* .IP name +/* Either the path to the SQLite configuration file (if it starts +/* with '/' or '.'), or the prefix which will be used to obtain +/* main.cf configuration parameters for this search. +/* +/* In the first case, the configuration parameters below are +/* specified in the file as \fIname\fR=\fBvalue\fR pairs. +/* +/* In the second case, the configuration parameters are +/* prefixed with the value of \fIname\fR and an underscore, +/* and they are specified in main.cf. For example, if this +/* value is \fIsqlitecon\fR, the parameters would look like +/* \fIsqlitecon_user\fR, \fIsqlitecon_table\fR, and so on. +/* +/* .IP open_flags +/* Must be O_RDONLY. +/* .IP dict_flags +/* See dict_open(3). +/* .PP +/* Configuration parameters: +/* +/* The parameters encodes a number of pieces of information: +/* dbpath, query, result_format and expansion_limit: +/* .IP \fIdbpath\fR +/* Path to SQLite database +/* .IP \fIquery\fR +/* Query template, before the query is actually issued, variable +/* substitutions are performed. See sqlite_table(5) for details. +/* .IP \fIresult_format\fR +/* The format used to expand results from queries. Substitutions +/* are performed as described in sqlite_table(5). Defaults to returning +/* the lookup result unchanged. +/* .IP expansion_limit +/* Limit (if any) on the total number of lookup result values. Lookups which +/* exceed the limit fail with dict_errno=DICT_ERR_RETRY. Note that each +/* non-empty (and non-NULL) column of a multi-column result row counts as +/* one result. +/* +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Axel Steiner +/* ast@@treibsand.com +/*--*/ + +/* System library. */ +#include "sys_defs.h" + +#ifdef HAS_SQLITE +#include + +#if !defined(SQLITE_VERSION_NUMBER) || (SQLITE_VERSION_NUMBER < 3005004) +#error "Your SQLite version is too old" +#endif + +/* Utility library. */ + +#include "msg.h" +#include "dict.h" +#include "vstring.h" +#include "stringops.h" +#include "mymalloc.h" + +/* Global library. */ + +#include "cfg_parser.h" +#include "db_common.h" + +/* Application-specific. */ + +#include "dict_sqlite.h" + +typedef struct { + DICT dict; + CFG_PARSER *parser; + sqlite3 *db; + char *dbpath; + char *query; + char *result_format; + int expansion_limit; + void *ctx; +} DICT_SQLITE; + +typedef sqlite3_stmt *SQL; + +/* internal function declarations */ + +static const char *dict_sqlite_lookup(DICT *, const char *); +DICT *dict_sqlite_open(const char *, int, int); +static void dict_sqlite_close(DICT *); +static void sqlite_parse_config(DICT_SQLITE *, const char *); + +/* dict_sqlite_quote - escape SQL metacharacters in input string */ + +static void dict_sqlite_quote(DICT *dict, const char *name, VSTRING *result) { + DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict; + int len = strlen(name); + int buflen = 2*len + 1; + char *q; + + if (buflen < len) + msg_panic("dict_sqlite_quote: integer overflow in 2*%d+1", len); + + VSTRING_SPACE(result, buflen); + q = sqlite3_mprintf("%q",name); + vstring_strncat(result,q, strlen(q)); + sqlite3_free(q); + VSTRING_SKIP(result); +} + + +/* dict_sqlite_close - close the database */ + +static void dict_sqlite_close(DICT *dict) { + const char *myname = "dict_sqlite_close"; + DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict; + + if (msg_verbose) + msg_info("%s: dict_sqlite_close", myname); + if (sqlite3_close(dict_sqlite->db) != SQLITE_OK) + msg_fatal("%s: DB close failed", myname); + cfg_parser_free(dict_sqlite->parser); + myfree(dict_sqlite->dbpath); + myfree(dict_sqlite->query); + myfree(dict_sqlite->result_format); + if (dict_sqlite->ctx) + db_common_free_ctx(dict_sqlite->ctx); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + + +/* dict_sqlite_lookup - find database entry */ + +static const char *dict_sqlite_lookup(DICT *dict, const char *name) { + const char *myname = "dict_sqlite_lookup"; + DICT_SQLITE *dict_sqlite = (DICT_SQLITE *) dict; + SQL sql; + const char *zErrMsg; + static VSTRING *query; + static VSTRING *result; + const char *r; + int expansion = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + if (db_common_check_domain(dict_sqlite->ctx, name) == 0) { + if (msg_verbose) + msg_info("%s: Skipping lookup of '%s'", myname, name); + return (0); + } + +#define INIT_VSTR(buf, len) do { \ + if (buf == 0) \ + buf = vstring_alloc(len); \ + VSTRING_RESET(buf); \ + VSTRING_TERMINATE(buf); \ + } while (0) + + INIT_VSTR(query, 10); + + if (!db_common_expand(dict_sqlite->ctx, dict_sqlite->query, + name, 0, query, dict_sqlite_quote)) + return (0); + + if (msg_verbose) + msg_info("%s: %s: Searching with query %s", myname, + dict_sqlite->parser->name, vstring_str(query)); + + if(sqlite3_prepare_v2(dict_sqlite->db,vstring_str(query),-1,&sql,&zErrMsg)!=SQLITE_OK) { + msg_fatal("%s: sql prepare %s\n",myname,sqlite3_errmsg(dict_sqlite->db)); + } + + INIT_VSTR(result, 10); + while (sqlite3_step(sql) == SQLITE_ROW ) { + if (db_common_expand(dict_sqlite->ctx, dict_sqlite->result_format, + sqlite3_column_text(sql, 0), name, result, 0) + && dict_sqlite->expansion_limit > 0 + && ++expansion > dict_sqlite->expansion_limit) { + msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", + myname, dict_sqlite->parser->name, name); + dict_errno = DICT_ERR_RETRY; + break; + } + } + + if(sqlite3_finalize(sql)){ + msg_fatal("%s: sql finalize for %s; %s\n",myname,vstring_str(query),sqlite3_errmsg(dict_sqlite->db)); + return(0); + } + + + r = vstring_str(result); + return ((dict_errno == 0 && *r) ? r : 0); +} + +/* sqlite_parse_config - parse sqlite configuration file */ + +static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf) { + CFG_PARSER *p; + VSTRING *buf; + + p = dict_sqlite->parser = cfg_parser_alloc(sqlitecf); + dict_sqlite->dbpath = cfg_get_str(p, "dbpath", "", 1, 0); + dict_sqlite->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); + + if ((dict_sqlite->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) { + buf = vstring_alloc(64); + db_common_sql_build_query(buf, p); + dict_sqlite->query = vstring_export(buf); + } + dict_sqlite->expansion_limit = cfg_get_int(p,"expansion_limit", 0, 0, 0); + dict_sqlite->ctx = 0; + + (void) db_common_parse(&dict_sqlite->dict, &dict_sqlite->ctx, dict_sqlite->query, 1); + (void) db_common_parse(0, &dict_sqlite->ctx, dict_sqlite->result_format, 0); + + db_common_parse_domain(p, dict_sqlite->ctx); + + if (dict_sqlite->dict.flags & DICT_FLAG_FOLD_FIX) + dict_sqlite->dict.fold_buf = vstring_alloc(10); + +} + +/* dict_sqlite_open - open sqlite database */ + +DICT *dict_sqlite_open(const char *name, int open_flags, int dict_flags) { + DICT_SQLITE *dict_sqlite; + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + msg_fatal("%s:%s map requires O_RDONLY access mode", DICT_TYPE_SQLITE, name); + + dict_sqlite = (DICT_SQLITE *) dict_alloc(DICT_TYPE_SQLITE, name, sizeof(DICT_SQLITE)); + dict_sqlite->dict.lookup = dict_sqlite_lookup; + dict_sqlite->dict.close = dict_sqlite_close; + dict_sqlite->dict.flags = dict_flags; + dict_sqlite->dict.flags |= DICT_FLAG_FIXED; + sqlite_parse_config(dict_sqlite, name); + + if (sqlite3_open(dict_sqlite->dbpath, &dict_sqlite->db)) { + msg_fatal("Can't open database: %s\n", sqlite3_errmsg(dict_sqlite->db)); + sqlite3_close(dict_sqlite->db); + } + + return (DICT_DEBUG (&dict_sqlite->dict)); +} +#endif Index: src/global/dict_sqlite.h --- /dev/null 2009-07-05 21:16:41 +0200 +++ src/global/dict_sqlite.h 2009-07-05 21:16:55 +0200 @@@@ -0,0 +1,32 @@@@ +#ifndef _DICT_SQLITE_H_INCLUDED_ +#define _DICT_SQLITE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_sqlite 3h +/* SUMMARY +/* dictionary manager interface to sqlite databases +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_SQLITE "sqlite" + +extern DICT *dict_sqlite_open(const char *, int, int); + + +/* AUTHOR(S) +/* Axel Steiner +/* ast@@treibsand.com +/*--*/ + +#endif Index: src/global/mail_dict.c --- src/global/mail_dict.c.orig 2008-01-08 22:07:47 +0100 +++ src/global/mail_dict.c 2009-07-05 21:16:55 +0200 @@@@ -36,6 +36,7 @@@@ #include #include #include +#include #include typedef struct { @@@@ -54,6 +55,9 @@@@ #ifdef HAS_PGSQL DICT_TYPE_PGSQL, dict_pgsql_open, #endif +#ifdef HAS_SQLITE + DICT_TYPE_SQLITE, dict_sqlite_open, +#endif 0, }; @