I am submitting the attached code (patches to some existing files and two new files: dict_odbc.c and dict_odbc.h) which adds support for a new dictionary type that uses unixODBC (www.unixodbc.org).
As noted in the past on this list, that could be useful in supporting map types that don't have native support. The code has been based on the dict_mysql. The main differences are that: - ODBC uses data source names instead of hostnames to connect to (I used the parameter name dsns) - ODBC supports only a few parameters like DSN name, username and password, the others like database name, server name etc are driver specific and not appropriate to include in a general implementation. I have added a parameter called add_con_params where anything driver specific can be added. Alternatively, many parameters can be setup in the odbc configuration itself (e.g. /etc/odbc.ini) - ODBC api does not have it's own quoting function - ODBC has some limitations about characters that can be used in the connection parameters. For sure the ; is a separator, so parameters that use it have to be enclosed in curly braces {}. For this reason usernames and passwords cannot begin or end with curly braces. Other characters may give problems depending on the driver. I have done some limited testing, but although I know of no problems it would probably be wise to consider this as non production code until it undergoes wider testing. I hope you may consider it for inclusion in postfix, but it may need some tidying up since I am not an expert in the use of vstring and may not always have chosen the most elegant way to accomplish things, though the base code from dict_mysql was an enormouse help. I updated the documentation in the source file. I imagine that other documentation like readme and odbc_table will be needed. I will look into that too. As a quick start, providing other parameters like hostname and database are configured in the dsn setup (e.g. /etc/odbc.ini), then the minimum postfix map configuration file may contain something like this: user = xxxxx password = yyyyyyyyy dsns = postfix query = SELECT domain FROM domain WHERE transport='virtual' and domain='%s' and active=1 Hope the files make it through the list ok. If not I'll publish them somewhere. John
/*++ /* NAME /* dict_odbc 3 /* SUMMARY /* dictionary manager interface to unixODBC /* SYNOPSIS /* #include <dict_odbc.h> /* /* DICT *dict_odbc_open(name, open_flags, dict_flags) /* const char *name; /* int open_flags; /* int dict_flags; /* DESCRIPTION /* dict_odbc_open() creates a dictionary of type 'odbc'. This /* dictionary is an interface for the postfix key->value mappings /* to unixODBC. The result is a pointer to the installed dictionary, /* or a null pointer in case of problems. /* /* The odbc dictionary can manage multiple connections to different /* sql servers on different hosts by configuring multiple dsns /* (data source names). It assumes that the underlying data /* on each host is identical (mirrored) and maintains one connection /* at any given time. If any connection fails, any other available /* ones will be opened and used. The intent of this feature is to eliminate /* a single point of failure for mail systems that would otherwise rely /* on a single sql server. /* .PP /* Arguments: /* .IP name /* Either the path to the ODBC 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=\fIvalue\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 \fIodbcsource\fR, the parameters would look like /* \fIodbcsource_user\fR, \fIodbcsource_table\fR, and so on. /* /* .IP other_name /* reference for outside use. /* .IP open_flags /* Must be O_RDONLY. /* .IP dict_flags /* See dict_open(3). /* .PP /* Configuration parameters: /* .IP user /* Username for connecting to the database. /* .IP password /* Password for the above. /* .IP add_con_params /* Additional parameters for connection string. These will be /* driver specific. Multiple parameters may be separated by ; e.g. /* database=xxxxxx;server=127.0.0.1 /* .IP domain /* List of domains the queries should be restricted to. If /* specified, only FQDN addresses whose domain parts matching this /* list will be queried against the SQL database. Lookups for /* partial addresses are also supressed. This can significantly /* reduce the query load on the server. /* .IP query /* Query template, before the query is actually issued, variable /* substitutions are performed. See odbc_table(5) for details. If /* No query is specified, the legacy variables \fItable\fR, /* \fIselect_field\fR, \fIwhere_field\fR and \fIadditional_conditions\fR /* are used to construct the query template. /* .IP result_format /* The format used to expand results from queries. Substitutions /* are performed as described in odbc_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->error=DICT_ERR_RETRY. Note that each /* non-empty (and non-NULL) column of a multi-column result row counts as /* one result. /* .IP table /* When \fIquery\fR is not set, name of the table used to construct the /* query string. This provides compatibility with older releases. /* .IP select_field /* When \fIquery\fR is not set, name of the result field used to /* construct the query string. This provides compatibility with older /* releases. /* .IP where_field /* When \fIquery\fR is not set, name of the where clause field used to /* construct the query string. This provides compatibility with older /* releases. /* .IP additional_conditions /* When \fIquery\fR is not set, additional where clause conditions used /* to construct the query string. This provides compatibility with older /* releases. /* .IP dsns /* List of dsns (data source names) to connect to. //* .PP /* 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 '<some username>' /* 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: /* .PP /* user = vmailer /* .br /* password = passwd /* .br /* dbname = vmailer_info /* .br /* table = aliases /* .br /* select_field = forw_addr /* .br /* where_field = alias /* .br /* dsns = dsn1\fR \fBdsn2 /* .IP additional_conditions /* Backward compatibility when \fIquery\fR is not set, additional /* conditions to the WHERE clause. /* .PP /* SEE ALSO /* dict(3) generic dictionary manager /* AUTHOR(S) /* This code submission was derived from /* dict_mysql by: /* Scott Cotton /* IC Group, Inc. /* sc...@icgroup.com /* /* Joshua Marcus /* IC Group, Inc. /* j...@icgroup.com /* /* dict_odbc submission by: /* John Fawcett /* j...@voipsupport.it /* /*--*/ /* System library. */ #include "sys_defs.h" #ifdef HAS_ODBC #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <syslog.h> #include <time.h> #include <sql.h> #include <sqlext.h> #ifdef STRCASECMP_IN_STRINGS_H #include <strings.h> #endif /* Utility library. */ #include "dict.h" #include "msg.h" #include "mymalloc.h" #include "argv.h" #include "vstring.h" #include "split_at.h" #include "find_inet.h" #include "myrand.h" #include "events.h" #include "stringops.h" /* Global library. */ #include "cfg_parser.h" #include "db_common.h" /* Application-specific. */ #include "dict_odbc.h" /* need some structs to help organize things */ typedef struct { SQLHENV *env; SQLHDBC *dbc; char *dsn_name; unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */ time_t ts; /* used for attempting reconnection * every so often if a dsn is not connected */ } DSN; typedef struct { int len_dsns; /* number of dsn */ DSN **db_dsns; /* the dsns for the database */ } PLODBC; typedef struct { DICT dict; CFG_PARSER *parser; char *query; char *result_format; void *ctx; int expansion_limit; char *username; char *password; char *add_con_params; ARGV *dsns; PLODBC *pldb; DSN *active_dsn; } DICT_ODBC; #define STATACTIVE (1<<0) #define STATFAIL (1<<1) #define STATUNTRIED (1<<2) #define RETRY_CONN_MAX 100 #define RETRY_CONN_INTV 60 /* 1 minute */ #define IDLE_CONN_INTV 60 /* 1 minute */ /* internal function declarations */ static PLODBC *plodbc_init(ARGV *); static SQLHSTMT *plodbc_query(DICT_ODBC *, const char *, VSTRING *); static void plodbc_dealloc(PLODBC *); static void plodbc_close_dsn(DSN *); static void plodbc_down_dsn(DSN *); static void plodbc_connect_single(DICT_ODBC *, DSN *); static const char *dict_odbc_lookup(DICT *, const char *); DICT *dict_odbc_open(const char *, int, int); static void dict_odbc_close(DICT *); static void odbc_parse_config(DICT_ODBC *, const char *); static DSN *dsn_init(const char *); static VSTRING * odbc_extract_error(SQLHANDLE, SQLSMALLINT); static void odbc_escape_string(char *, const char *, unsigned long); /* dict_odbc_quote - escape SQL metacharacters in input string */ static void dict_odbc_quote(DICT *dict, const char *name, VSTRING *result) { DICT_ODBC *dict_odbc = (DICT_ODBC *) dict; int len = strlen(name); int buflen = 2 * len + 1; /* * We won't get integer overflows in 2*len + 1, because Postfix input * keys have reasonable size limits, better safe than sorry. */ if (buflen < len) msg_panic("dict_odbc_quote: integer overflow in 2*%d+1", len); VSTRING_SPACE(result, buflen); odbc_escape_string(vstring_end(result),name,len); if (msg_verbose) msg_info("dict_odbc: quoted %s", vstring_str(result)); VSTRING_SKIP(result); } static void odbc_escape_string(char *to, const char *from, unsigned long length) { const char * end; //strncpy(to,from,length); //to[length]='\0'; for (end = from + length; from < end; from++) { char escape= 0; switch (*from) { case 0: escape= '0'; break; case '\\': escape= '\\'; break; case '\'': escape= '\''; break; case '"': escape= '"'; break; } if (escape) { *to++= '\\'; *to++= escape; } else { *to++= *from; } } *to= 0; } /* dict_odbc_lookup - find database entry */ #define ODBC_DATA_BUFFER_LEN 512 static const char *dict_odbc_lookup(DICT *dict, const char *name) { const char *myname = "dict_odbc_lookup"; DICT_ODBC *dict_odbc = (DICT_ODBC *) dict; SQLHSTMT *query_res; static VSTRING *result; static VSTRING *query; int i; int j; int expansion; const char *r; db_quote_callback_t quote_func = dict_odbc_quote; int domain_rc; SQLSMALLINT columns; /* number of columns in result-set */ SQLRETURN ret; /* ODBC API return status */ VSTRING * diag; dict->error = 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 there is a domain list for this map, then only search for addresses * in domains on the list. This can significantly reduce the load on the * server. */ if ((domain_rc = db_common_check_domain(dict_odbc->ctx, name)) == 0) { if (msg_verbose) msg_info("%s: Skipping lookup of '%s'", myname, name); return (0); } if (domain_rc < 0) DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 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); /* * Suppress the lookup if the query expansion is empty * * This initial expansion is outside the context of any specific dsn * connection, we just want to check the key pre-requisites, so when * quoting happens separately for each connection, we don't bother with * quoting... */ quote_func = 0; if (!db_common_expand(dict_odbc->ctx, dict_odbc->query, name, 0, query, quote_func)) return (0); /* do the query - set dict->error & cleanup if there's an error */ if ((query_res = plodbc_query(dict_odbc, name, query)) == 0) { dict->error = DICT_ERR_RETRY; return (0); } INIT_VSTR(result, 10); /* How many columns are there */ SQLNumResultCols(*query_res, &columns); if (msg_verbose) msg_info("Columns in result set %u ", columns); expansion = i = 0; while (SQL_SUCCEEDED(ret = SQLFetch(*query_res)) && dict->error == 0) { for (j = 1; j <= columns; j++) { SQLLEN indicator; char buf[ODBC_DATA_BUFFER_LEN]; /* retrieve column data as a string */ ret = SQLGetData(*query_res, j, SQL_C_CHAR, buf, sizeof(buf), &indicator); if (SQL_SUCCEEDED(ret)) { /* if column value is null then: indicator == SQL_NULL_DATA */ if (msg_verbose) msg_info("Row %u Column %u : %s", i, j, (indicator == SQL_NULL_DATA) ? "NULL" : buf); if (db_common_expand(dict_odbc->ctx, dict_odbc->result_format, (indicator == SQL_NULL_DATA) ? 0 : buf, name, result, 0) && dict_odbc->expansion_limit > 0 && ++expansion > dict_odbc->expansion_limit) { msg_warn("%s: %s: Expansion limit exceeded for key: '%s'", myname, dict_odbc->parser->name, name); dict->error = DICT_ERR_RETRY; break; } } else { diag=odbc_extract_error(*query_res, SQL_HANDLE_STMT); msg_warn("SQLGetData Error: %s", vstring_str(diag)); vstring_free(diag); } } i++; } /* i contains number of rows */ if (msg_verbose) msg_info("%s: retrieved %d rows", myname, i); if (i == 0) { SQLFreeStmt(*query_res,SQL_CLOSE); SQLFreeHandle(SQL_HANDLE_STMT, *query_res); myfree((char *) (query_res)); return 0; } SQLFreeStmt(*query_res,SQL_CLOSE); SQLFreeHandle(SQL_HANDLE_STMT, *query_res); myfree((char *) (query_res)); r = vstring_str(result); return ((dict->error == 0 && *r) ? r : 0); } /* dict_odbc_check_stat - check the status of a dsn */ static int dict_odbc_check_stat(DSN *dsn, unsigned stat, time_t t) { if (dsn->stat & stat) { /* try not to hammer the dead dsns too often */ if (dsn->stat == STATFAIL && dsn->ts > 0 && dsn->ts >= t) return 0; return 1; } return 0; } /* dict_odbc_find_dsn - find a dsn with the given status */ static DSN *dict_odbc_find_dsn(PLODBC *PLDB, unsigned stat) { time_t t; int count = 0; int idx; int i; t = time((time_t *) 0); for (i = 0; i < PLDB->len_dsns; i++) { if (dict_odbc_check_stat(PLDB->db_dsns[i], stat, t)) count++; } if (count) { idx = (count > 1) ? 1 + count * (double) myrand() / (1.0 + RAND_MAX) : 1; for (i = 0; i < PLDB->len_dsns; i++) { if (dict_odbc_check_stat(PLDB->db_dsns[i], stat, t) && --idx == 0) return PLDB->db_dsns[i]; } } return 0; } /* dict_odbc_get_active - get an active connection */ static DSN *dict_odbc_get_active(DICT_ODBC *dict_odbc) { const char *myname = "dict_odbc_get_active"; PLODBC *PLDB = dict_odbc->pldb; DSN *dsn; int count = RETRY_CONN_MAX; /* Try the active connections first */ if ((dsn = dict_odbc_find_dsn(PLDB, STATACTIVE)) != NULL ) { if (msg_verbose) msg_info("%s: found active connection to dsn %s", myname, dsn->dsn_name); return dsn; } /* * Try the remaining dsns. "count" is a safety net, in case the loop * takes more than RETRY_CONN_INTV and the dead dsns are no longer * skipped. */ while (--count > 0 && ((dsn = dict_odbc_find_dsn(PLDB, STATUNTRIED | STATFAIL )) != NULL)) { if (msg_verbose) msg_info("%s: attempting to connect to dsn %s", myname, dsn->dsn_name); plodbc_connect_single(dict_odbc, dsn); if (dsn->stat == STATACTIVE) return dsn; } /* bad news... */ return 0; } /* dict_odbc_event - callback: close idle connections */ static void dict_odbc_event(int unused_event, char *context) { DSN *dsn = (DSN *) context; if (dsn->dbc) plodbc_close_dsn(dsn); } /* * plodbc_query - process an ODBC query. Return SQLHSTMT * on success. * On failure, log failure and try other db instances. * on failure of all db instances, return 0; * close unnecessary active connections */ static SQLHSTMT *plodbc_query(DICT_ODBC *dict_odbc, const char *name, VSTRING *query) { DSN *dsn; SQLHSTMT *res = 0; VSTRING * diag; SQLRETURN ret; /* ODBC API return status */ if (msg_verbose) msg_info("dict_odbc: query %s", vstring_str(query)); while ((dsn = dict_odbc_get_active(dict_odbc)) != NULL) { dict_odbc->active_dsn = dsn; VSTRING_RESET(query); VSTRING_TERMINATE(query); db_common_expand(dict_odbc->ctx, dict_odbc->query, name, 0, query, dict_odbc_quote); dict_odbc->active_dsn = 0; if (msg_verbose) msg_info("dict_odbc: query after escaping %s", vstring_str(query)); if (msg_verbose) msg_info("active connection %s", dsn->dsn_name); if (dsn->env==0) msg_info("no dsn->env"); if (dsn->dbc==0) msg_info("no dsn->dbc"); if ((res = (SQLHSTMT *) mymalloc(sizeof(SQLHSTMT))) == 0) msg_fatal("mymalloc of ODBC SQLHSTMT failed"); if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, *dsn->dbc, res))) msg_fatal("dict_odbc: unable to open statement handle"); if (!SQL_SUCCEEDED(SQLExecDirect(*res, vstring_str(query), SQL_NTS))) { diag=odbc_extract_error(*res, SQL_HANDLE_STMT); msg_warn("odbc query failed: %s %s", vstring_str(query),vstring_str(diag)); vstring_free(diag); plodbc_down_dsn(dsn); } else { if (msg_verbose) msg_info("dict_odbc: successful query from dsn %s", dsn->dsn_name); event_request_timer(dict_odbc_event, (char *) dsn, IDLE_CONN_INTV); break; } } return res; } /* * plodbc_connect_single - * used to reconnect to a single database when one is down or none is * connected yet. Log all errors and set the stat field of dsn accordingly */ static void plodbc_connect_single(DICT_ODBC *dict_odbc, DSN *dsn) { SQLRETURN ret; /* ODBC API return status */ SQLCHAR outstr[1024]; SQLSMALLINT outstrlen; static VSTRING *con_str; static VSTRING *diag; /* Allocate an environment handle */ if ((dsn->env = (SQLHENV *) mymalloc(sizeof(SQLHENV))) == 0) msg_fatal("mymalloc of ODBC SQLHENV failed"); if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, dsn->env))) msg_fatal("dict_odbc: unable to open environment handle"); if(msg_verbose) msg_info("open environment handle succeeded"); /* We want ODBC 3 support */ if (!SQL_SUCCEEDED(SQLSetEnvAttr(*dsn->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0))) msg_warn("dict_odbc: unable to set ODBC 3 environment attribute"); /* Allocate a connection handle */ if ((dsn->dbc = (SQLHDBC *) mymalloc(sizeof(SQLHDBC))) == 0) msg_fatal("mymalloc of ODBC SQLHDBC failed"); if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_DBC, *dsn->env, dsn->dbc))) msg_fatal("dict_odbc: unable to open connection handle"); if(msg_verbose) msg_info("open connection handle succeeded"); /* Setup connection string */ con_str = vstring_alloc(100); vstring_strcpy(con_str,"DSN="); vstring_strcat(con_str,dsn->dsn_name); vstring_strcat(con_str,";UID="); vstring_strcat(con_str,dict_odbc->username); vstring_strcat(con_str,";PWD="); vstring_strcat(con_str,dict_odbc->password); if (strlen(dict_odbc->add_con_params)>0) { vstring_strcat(con_str,";"); vstring_strcat(con_str,dict_odbc->add_con_params); } if(msg_verbose) msg_info("connection string %s",vstring_str(con_str)); /* Connect to the DSN */ ret = SQLDriverConnect(*dsn->dbc, NULL, vstring_str(con_str), SQL_NTS, outstr, sizeof(outstr), &outstrlen, SQL_DRIVER_NOPROMPT); if (SQL_SUCCEEDED(ret)) { if (msg_verbose) msg_info("dict_odbc: successful connection to dsn %s", dsn->dsn_name); dsn->stat = STATACTIVE; if (ret == SQL_SUCCESS_WITH_INFO) { if (msg_verbose) { diag=odbc_extract_error(*dsn->dbc, SQL_HANDLE_DBC); msg_info("dict_odbc: driver reported diagnostics %s", vstring_str(diag)); vstring_free(diag); } } } else { diag=odbc_extract_error(*dsn->dbc, SQL_HANDLE_DBC); msg_warn("cannot connect to odbc dsn %s: %s", dsn->dsn_name, vstring_str(diag)); vstring_free(diag); plodbc_down_dsn(dsn); } } /* plodbc_close_dsn - close an established ODBC connection */ static void plodbc_close_dsn(DSN *dsn) { SQLDisconnect(*dsn->dbc); SQLFreeHandle(SQL_HANDLE_DBC, *dsn->dbc); myfree((char *) (dsn->dbc)); dsn->dbc = 0; SQLFreeHandle(SQL_HANDLE_ENV, *dsn->env); myfree((char *) (dsn->env)); dsn->env = 0; dsn->stat = STATUNTRIED; } /* * plodbc_down_dsn - close a failed connection AND set a "stay away from * this dsn" timer */ static void plodbc_down_dsn(DSN *dsn) { SQLDisconnect(*dsn->dbc); SQLFreeHandle(SQL_HANDLE_DBC, *dsn->dbc); myfree((char *) (dsn->dbc)); dsn->dbc = 0; SQLFreeHandle(SQL_HANDLE_ENV, *dsn->env); myfree((char *) (dsn->env)); dsn->env = 0; dsn->ts = time((time_t *) 0) + RETRY_CONN_INTV; dsn->stat = STATFAIL; event_cancel_timer(dict_odbc_event, (char *) dsn); } /* odbc_parse_config - parse odbc configuration file */ static void odbc_parse_config(DICT_ODBC *dict_odbc, const char *odbccf) { const char *myname = "odbc_parse_config"; CFG_PARSER *p = dict_odbc->parser; VSTRING *buf; char *dsns; dict_odbc->username = cfg_get_str(p, "user", "", 0, 0); dict_odbc->password = cfg_get_str(p, "password", "", 0, 0); dict_odbc->add_con_params = cfg_get_str(p, "add_con_params", "", 0, 0); dict_odbc->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); /* * XXX: The default should be non-zero for safety, but that is not * backwards compatible. */ dict_odbc->expansion_limit = cfg_get_int(dict_odbc->parser, "expansion_limit", 0, 0, 0); if ((dict_odbc->query = cfg_get_str(p, "query", NULL, 0, 0)) == 0) { /* * No query specified -- fallback to building it from components (old * style "select %s from %s where %s") */ buf = vstring_alloc(64); db_common_sql_build_query(buf, p); dict_odbc->query = vstring_export(buf); } /* * Must parse all templates before we can use db_common_expand() */ dict_odbc->ctx = 0; (void) db_common_parse(&dict_odbc->dict, &dict_odbc->ctx, dict_odbc->query, 1); (void) db_common_parse(0, &dict_odbc->ctx, dict_odbc->result_format, 0); db_common_parse_domain(p, dict_odbc->ctx); /* * Maps that use substring keys should only be used with the full input * key. */ if (db_common_dict_partial(dict_odbc->ctx)) dict_odbc->dict.flags |= DICT_FLAG_PATTERN; else dict_odbc->dict.flags |= DICT_FLAG_FIXED; if (dict_odbc->dict.flags & DICT_FLAG_FOLD_FIX) dict_odbc->dict.fold_buf = vstring_alloc(10); dsns = cfg_get_str(p, "dsns", "", 0, 0); dict_odbc->dsns = argv_split(dsns, " ,\t\r\n"); if (dict_odbc->dsns->argc == 0) { argv_add(dict_odbc->dsns, "default", ARGV_END); argv_terminate(dict_odbc->dsns); if (msg_verbose) msg_info("%s: %s: no dsns specified, defaulting to '%s'", myname, odbccf, dict_odbc->dsns->argv[0]); } myfree(dsns); } /* dict_odbc_open - open ODBC database */ DICT *dict_odbc_open(const char *name, int open_flags, int dict_flags) { DICT_ODBC *dict_odbc; CFG_PARSER *parser; /* * Sanity checks. */ if (open_flags != O_RDONLY) return (dict_surrogate(DICT_TYPE_ODBC, name, open_flags, dict_flags, "%s:%s map requires O_RDONLY access mode", DICT_TYPE_ODBC, name)); /* * Open the configuration file. */ if ((parser = cfg_parser_alloc(name)) == 0) return (dict_surrogate(DICT_TYPE_ODBC, name, open_flags, dict_flags, "open %s: %m", name)); dict_odbc = (DICT_ODBC *) dict_alloc(DICT_TYPE_ODBC, name, sizeof(DICT_ODBC)); dict_odbc->dict.lookup = dict_odbc_lookup; dict_odbc->dict.close = dict_odbc_close; dict_odbc->dict.flags = dict_flags; dict_odbc->parser = parser; odbc_parse_config(dict_odbc, name); dict_odbc->active_dsn = 0; dict_odbc->pldb = plodbc_init(dict_odbc->dsns); if (dict_odbc->pldb == NULL) msg_fatal("couldn't intialize pldb!\n"); dict_odbc->dict.owner = cfg_get_owner(dict_odbc->parser); return (DICT_DEBUG (&dict_odbc->dict)); } /* * plodbc_init - initalize a ODBC database. * Return NULL on failure, or a PLODBC * on success. */ static PLODBC *plodbc_init(ARGV *dsns) { PLODBC *PLDB; int i; if ((PLDB = (PLODBC *) mymalloc(sizeof(PLODBC))) == 0) msg_fatal("mymalloc of pldb failed"); PLDB->len_dsns = dsns->argc; if ((PLDB->db_dsns = (DSN **) mymalloc(sizeof(DSN *) * dsns->argc)) == 0) return (0); for (i = 0; i < dsns->argc; i++) PLDB->db_dsns[i] = dsn_init(dsns->argv[i]); return PLDB; } /* dsn_init - initialize DSN structure */ static DSN *dsn_init(const char *dsn_name) { const char *myname = "odbc dsn_init"; DSN *dsn = (DSN *) mymalloc(sizeof(DSN)); dsn->env = 0; dsn->dbc = 0; dsn->dsn_name = mystrdup(dsn_name); dsn->stat = STATUNTRIED; dsn->ts = 0; if (msg_verbose > 1) msg_info("%s: dsn=%s", myname, dsn->dsn_name); return dsn; } /* dict_odbc_close - close ODBC database */ static void dict_odbc_close(DICT *dict) { DICT_ODBC *dict_odbc = (DICT_ODBC *) dict; plodbc_dealloc(dict_odbc->pldb); cfg_parser_free(dict_odbc->parser); myfree(dict_odbc->username); myfree(dict_odbc->password); myfree(dict_odbc->add_con_params); myfree(dict_odbc->query); myfree(dict_odbc->result_format); if (dict_odbc->dsns) argv_free(dict_odbc->dsns); if (dict_odbc->ctx) db_common_free_ctx(dict_odbc->ctx); if (dict->fold_buf) vstring_free(dict->fold_buf); dict_free(dict); } /* plodbc_dealloc - free memory associated with PLODBC close databases */ static void plodbc_dealloc(PLODBC *PLDB) { int i; for (i = 0; i < PLDB->len_dsns; i++) { event_cancel_timer(dict_odbc_event, (char *) (PLDB->db_dsns[i])); if (PLDB->db_dsns[i]->dbc) { SQLFreeHandle(SQL_HANDLE_DBC, *PLDB->db_dsns[i]->dbc); myfree((char *) PLDB->db_dsns[i]->dbc); } if (PLDB->db_dsns[i]->env) { SQLFreeHandle(SQL_HANDLE_ENV, *PLDB->db_dsns[i]->env); myfree((char *) PLDB->db_dsns[i]->env); } myfree(PLDB->db_dsns[i]->dsn_name); myfree((char *) PLDB->db_dsns[i]); } myfree((char *) PLDB->db_dsns); myfree((char *) (PLDB)); } /* odbc_extract_error - extracts error records from odbc concatenating into a VSTRING. */ #define ODBC_ERROR_BUFFER_LEN 256 #define ODBC_ERROR_STATE_LEN 10 static VSTRING * odbc_extract_error(SQLHANDLE handle, SQLSMALLINT type) { SQLINTEGER i = 0; SQLINTEGER native; SQLCHAR state[ODBC_ERROR_STATE_LEN]; SQLCHAR text[ODBC_ERROR_BUFFER_LEN]; SQLSMALLINT len; SQLRETURN ret; VSTRING * diag; diag = vstring_alloc(100); /* While loop is needed becasue there can be multiple diagnostic records */ do { ret = SQLGetDiagRec(type, handle, ++i, state, &native, text, sizeof(text), &len ); /* for safety make sure the strings are null terminated */ text[ODBC_ERROR_BUFFER_LEN-1]='\0'; state[ODBC_ERROR_STATE_LEN-1]='\0'; if (SQL_SUCCEEDED(ret)) { vstring_sprintf_append(diag,"Diagnostic record %s:%ld:%ld:%s", state, (long) i, (long) native, text); } } while( ret == SQL_SUCCESS ); return diag; } #endif
#ifndef _DICT_ODBC_H_INCLUDED_ #define _DICT_ODBC_H_INCLUDED_ /*++ /* NAME /* dict_odbc 3h /* SUMMARY /* dictionary manager interface to unixODBC /* SYNOPSIS /* #include <dict_odbc.h> /* DESCRIPTION /* .nf /* * Utility library. */ #include <dict.h> /* * External interface. */ #define DICT_TYPE_ODBC "odbc" extern DICT *dict_odbc_open(const char *, int, int); /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* This code submission was derived from /* dict_mysql by: /* Scott Cotton /* IC Group, Inc. /* sc...@icgroup.com /* /* Joshua Marcus /* IC Group, Inc. /* j...@icgroup.com /* /* dict_odbc submission by: /* John Fawcett /* j...@voipsupport.it /*--*/ #endif
--- postfix-2.12-20140406_orig/src/global/mail_dict.c 2011-12-19 20:55:38.000000000 +0100 +++ postfix-2.12-20140406/src/global/mail_dict.c 2014-04-24 23:41:04.000000000 +0200 @@ -35,6 +35,7 @@ #include <dict_proxy.h> #include <dict_ldap.h> #include <dict_mysql.h> +#include <dict_odbc.h> #include <dict_pgsql.h> #include <dict_sqlite.h> #include <dict_memcache.h> @@ -53,6 +54,9 @@ #ifdef HAS_MYSQL DICT_TYPE_MYSQL, dict_mysql_open, #endif +#ifdef HAS_ODBC + DICT_TYPE_ODBC, dict_odbc_open, +#endif #ifdef HAS_PGSQL DICT_TYPE_PGSQL, dict_pgsql_open, #endif --- postfix-2.12-20140406_orig/src/global/Makefile.in 2014-03-22 23:18:02.000000000 +0100 +++ postfix-2.12-20140406/src/global/Makefile.in 2014-04-25 12:57:30.000000000 +0200 @@ -3,7 +3,7 @@ canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \ 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 \ + deliver_request.c dict_ldap.c dict_mysql.c dict_odbc.c dict_pgsql.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 \ @@ -37,7 +37,7 @@ canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \ 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 \ + deliver_request.o dict_ldap.o dict_mysql.o dict_odbc.o dict_pgsql.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 \ @@ -71,7 +71,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 dict_sqlite.h domain_list.h \ + dict_ldap.h dict_mysql.h dict_odbc.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 \ @@ -949,7 +949,26 @@ dict_mysql.o: db_common.h dict_mysql.o: dict_mysql.c dict_mysql.o: dict_mysql.h -dict_mysql.o: string_list.h +dict_odbc.o: ../../include/argv.h +dict_odbc.o: ../../include/dict.h +dict_odbc.o: ../../include/events.h +dict_odbc.o: ../../include/find_inet.h +dict_odbc.o: ../../include/match_list.h +dict_odbc.o: ../../include/msg.h +dict_odbc.o: ../../include/myflock.h +dict_odbc.o: ../../include/mymalloc.h +dict_odbc.o: ../../include/myrand.h +dict_odbc.o: ../../include/split_at.h +dict_odbc.o: ../../include/stringops.h +dict_odbc.o: ../../include/sys_defs.h +dict_odbc.o: ../../include/vbuf.h +dict_odbc.o: ../../include/vstream.h +dict_odbc.o: ../../include/vstring.h +dict_odbc.o: cfg_parser.h +dict_odbc.o: db_common.h +dict_odbc.o: dict_odbc.c +dict_odbc.o: dict_odbc.h +dict_odbc.o: string_list.h dict_pgsql.o: ../../include/argv.h dict_pgsql.o: ../../include/dict.h dict_pgsql.o: ../../include/events.h @@ -1424,6 +1443,7 @@ mail_dict.o: dict_ldap.h mail_dict.o: dict_memcache.h mail_dict.o: dict_mysql.h +mail_dict.o: dict_odbc.h mail_dict.o: dict_pgsql.h mail_dict.o: dict_proxy.h mail_dict.o: dict_sqlite.h --- postfix-2.12-20140406_orig/src/postconf/postconf_dbms.c 2013-12-19 23:53:32.000000000 +0100 +++ postfix-2.12-20140406/src/postconf/postconf_dbms.c 2014-04-25 13:43:38.000000000 +0200 @@ -61,6 +61,7 @@ #include <dict_proxy.h> #include <dict_ldap.h> #include <dict_mysql.h> +#include <dict_odbc.h> #include <dict_pgsql.h> #include <dict_sqlite.h> #include <dict_memcache.h> --- postfix-2.12-20140406_orig/src/postconf/Makefile.in 2013-11-27 21:16:56.000000000 +0100 +++ postfix-2.12-20140406/src/postconf/Makefile.in 2014-04-25 13:44:37.000000000 +0200 @@ -803,6 +803,7 @@ postconf_dbms.o: ../../include/dict_ldap.h postconf_dbms.o: ../../include/dict_memcache.h postconf_dbms.o: ../../include/dict_mysql.h +postconf_dbms.o: ../../include/dict_odbc.h postconf_dbms.o: ../../include/dict_pgsql.h postconf_dbms.o: ../../include/dict_proxy.h postconf_dbms.o: ../../include/dict_sqlite.h