diff options
Diffstat (limited to '')
-rw-r--r-- | src/lookups/ibase.c | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/src/lookups/ibase.c b/src/lookups/ibase.c new file mode 100644 index 0000000..c4fff71 --- /dev/null +++ b/src/lookups/ibase.c @@ -0,0 +1,561 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* The code in this module was contributed by Ard Biesheuvel. */ + +#include "../exim.h" +#include "lf_functions.h" + +#include <ibase.h> /* The system header */ + +/* Structure and anchor for caching connections. */ + +typedef struct ibase_connection { + struct ibase_connection *next; + uschar *server; + isc_db_handle dbh; + isc_tr_handle transh; +} ibase_connection; + +static ibase_connection *ibase_connections = NULL; + + + +/************************************************* +* Open entry point * +*************************************************/ + +/* See local README for interface description. */ + +static void *ibase_open(const uschar * filename, uschar ** errmsg) +{ +return (void *) (1); /* Just return something non-null */ +} + + + +/************************************************* +* Tidy entry point * +*************************************************/ + +/* See local README for interface description. */ + +static void ibase_tidy(void) +{ + ibase_connection *cn; + ISC_STATUS status[20]; + + while ((cn = ibase_connections) != NULL) { + ibase_connections = cn->next; + DEBUG(D_lookup) debug_printf_indent("close Interbase connection: %s\n", + cn->server); + isc_commit_transaction(status, &cn->transh); + isc_detach_database(status, &cn->dbh); + } +} + +static int fetch_field(char *buffer, int buffer_size, XSQLVAR * var) +{ + if (buffer_size < var->sqllen) + return 0; + + switch (var->sqltype & ~1) { + case SQL_VARYING: + strncpy(buffer, &var->sqldata[2], *(short *) var->sqldata); + return *(short *) var->sqldata; + case SQL_TEXT: + strncpy(buffer, var->sqldata, var->sqllen); + return var->sqllen; + case SQL_SHORT: + return sprintf(buffer, "%d", *(short *) var->sqldata); + case SQL_LONG: + return sprintf(buffer, "%ld", *(ISC_LONG *) var->sqldata); +#ifdef SQL_INT64 + case SQL_INT64: + return sprintf(buffer, "%lld", *(ISC_INT64 *) var->sqldata); +#endif + default: + /* not implemented */ + return 0; + } +} + +/************************************************* +* Internal search function * +*************************************************/ + +/* This function is called from the find entry point to do the search for a +single server. + +Arguments: + query the query string + server the server string + resultptr where to store the result + errmsg where to point an error message + defer_break TRUE if no more servers are to be tried after DEFER + +The server string is of the form "host:dbname|user|password". The host can be +host:port. This string is in a nextinlist temporary buffer, so can be +overwritten. + +Returns: OK, FAIL, or DEFER +*/ + +static int +perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr, + uschar ** errmsg, BOOL * defer_break) +{ +isc_stmt_handle stmth = NULL; +XSQLDA *out_sqlda; +XSQLVAR *var; +int i; +rmark reset_point; + +char buffer[256]; +ISC_STATUS status[20], *statusp = status; + +gstring * result; +int yield = DEFER; +ibase_connection *cn; +uschar *server_copy = NULL; +uschar *sdata[3]; + +/* Disaggregate the parameters from the server argument. The order is host, +database, user, password. We can write to the string, since it is in a +nextinlist temporary buffer. The copy of the string that is used for caching +has the password removed. This copy is also used for debugging output. */ + +for (int i = 2; i > 0; i--) + { + uschar *pp = Ustrrchr(server, '|'); + + if (pp == NULL) + { + *errmsg = string_sprintf("incomplete Interbase server data: %s", + (i == 3) ? server : server_copy); + *defer_break = TRUE; + return DEFER; + } + *pp++ = 0; + sdata[i] = pp; + if (i == 2) + server_copy = string_copy(server); /* sans password */ + } +sdata[0] = server; /* What's left at the start */ + +/* See if we have a cached connection to the server */ + +for (cn = ibase_connections; cn != NULL; cn = cn->next) + if (Ustrcmp(cn->server, server_copy) == 0) + break; + +/* Use a previously cached connection ? */ + +if (cn) + { + static char db_info_options[] = { isc_info_base_level }; + + /* test if the connection is alive */ + if (isc_database_info(status, &cn->dbh, sizeof(db_info_options), + db_info_options, sizeof(buffer), buffer)) + { + /* error occurred: assume connection is down */ + DEBUG(D_lookup) + debug_printf + ("Interbase cleaning up cached connection: %s\n", + cn->server); + isc_detach_database(status, &cn->dbh); + } + else + DEBUG(D_lookup) debug_printf_indent("Interbase using cached connection for %s\n", + server_copy); + } +else + { + cn = store_get(sizeof(ibase_connection), GET_UNTAINTED); + cn->server = server_copy; + cn->dbh = NULL; + cn->transh = NULL; + cn->next = ibase_connections; + ibase_connections = cn; + } + +/* If no cached connection, we must set one up. */ + +if (cn->dbh == NULL || cn->transh == NULL) + { + char *dpb; + short dpb_length; + static char trans_options[] = + { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed, + isc_tpb_rec_version + }; + + /* Construct the database parameter buffer. */ + dpb = buffer; + *dpb++ = isc_dpb_version1; + *dpb++ = isc_dpb_user_name; + *dpb++ = strlen(sdata[1]); + for (char * p = sdata[1]; *p;) + *dpb++ = *p++; + *dpb++ = isc_dpb_password; + *dpb++ = strlen(sdata[2]); + for (char * p = sdata[2]; *p;) + *dpb++ = *p++; + dpb_length = dpb - buffer; + + DEBUG(D_lookup) + debug_printf_indent("new Interbase connection: database=%s user=%s\n", + sdata[0], sdata[1]); + + /* Connect to the database */ + if (isc_attach_database + (status, 0, sdata[0], &cn->dbh, dpb_length, buffer)) + { + isc_interprete(buffer, &statusp); + *errmsg = + string_sprintf("Interbase attach() failed: %s", buffer); + *defer_break = FALSE; + goto IBASE_EXIT; + } + + /* Now start a read-only read-committed transaction */ + if (isc_start_transaction + (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options), + trans_options)) + { + isc_interprete(buffer, &statusp); + isc_detach_database(status, &cn->dbh); + *errmsg = + string_sprintf("Interbase start_transaction() failed: %s", + buffer); + *defer_break = FALSE; + goto IBASE_EXIT; + } + } + +/* Run the query */ +if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) + { + isc_interprete(buffer, &statusp); + *errmsg = + string_sprintf("Interbase alloc_statement() failed: %s", + buffer); + *defer_break = FALSE; + goto IBASE_EXIT; + } + +/* Lacking any information, assume that the data is untainted */ +reset_point = store_mark(); +out_sqlda = store_get(XSQLDA_LENGTH(1), GET_UNTAINTED); +out_sqlda->version = SQLDA_VERSION1; +out_sqlda->sqln = 1; + +if (isc_dsql_prepare + (status, &cn->transh, &stmth, 0, query, 1, out_sqlda)) + { + isc_interprete(buffer, &statusp); + reset_point = store_reset(reset_point); + out_sqlda = NULL; + *errmsg = + string_sprintf("Interbase prepare_statement() failed: %s", + buffer); + *defer_break = FALSE; + goto IBASE_EXIT; + } + +/* re-allocate the output structure if there's more than one field */ +if (out_sqlda->sqln < out_sqlda->sqld) + { + XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), GET_UNTAINTED); + if (isc_dsql_describe + (status, &stmth, out_sqlda->version, new_sqlda)) + { + isc_interprete(buffer, &statusp); + isc_dsql_free_statement(status, &stmth, DSQL_drop); + reset_point = store_reset(reset_point); + out_sqlda = NULL; + *errmsg = string_sprintf("Interbase describe_statement() failed: %s", + buffer); + *defer_break = FALSE; + goto IBASE_EXIT; + } + out_sqlda = new_sqlda; + } + +/* allocate storage for every returned field */ +for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) + { + switch (var->sqltype & ~1) + { + case SQL_VARYING: + var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, GET_UNTAINTED); + break; + case SQL_TEXT: + var->sqldata = CS store_get(sizeof(char) * var->sqllen, GET_UNTAINTED); + break; + case SQL_SHORT: + var->sqldata = CS store_get(sizeof(short), GET_UNTAINTED); + break; + case SQL_LONG: + var->sqldata = CS store_get(sizeof(ISC_LONG), GET_UNTAINTED); + break; +#ifdef SQL_INT64 + case SQL_INT64: + var->sqldata = CS store_get(sizeof(ISC_INT64), GET_UNTAINTED); + break; +#endif + case SQL_FLOAT: + var->sqldata = CS store_get(sizeof(float), GET_UNTAINTED); + break; + case SQL_DOUBLE: + var->sqldata = CS store_get(sizeof(double), GET_UNTAINTED); + break; +#ifdef SQL_TIMESTAMP + case SQL_DATE: + var->sqldata = CS store_get(sizeof(ISC_QUAD), GET_UNTAINTED); + break; +#else + case SQL_TIMESTAMP: + var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP), GET_UNTAINTED); + break; + case SQL_TYPE_DATE: + var->sqldata = CS store_get(sizeof(ISC_DATE), GET_UNTAINTED); + break; + case SQL_TYPE_TIME: + var->sqldata = CS store_get(sizeof(ISC_TIME), GET_UNTAINTED); + break; + #endif + } + if (var->sqltype & 1) + var->sqlind = (short *) store_get(sizeof(short), GET_UNTAINTED); + } + +/* finally, we're ready to execute the statement */ +if (isc_dsql_execute + (status, &cn->transh, &stmth, out_sqlda->version, NULL)) + { + isc_interprete(buffer, &statusp); + *errmsg = string_sprintf("Interbase describe_statement() failed: %s", + buffer); + isc_dsql_free_statement(status, &stmth, DSQL_drop); + *defer_break = FALSE; + goto IBASE_EXIT; + } + +while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) != 100L) + { + /* check if an error occurred */ + if (status[0] & status[1]) + { + isc_interprete(buffer, &statusp); + *errmsg = + string_sprintf("Interbase fetch() failed: %s", buffer); + isc_dsql_free_statement(status, &stmth, DSQL_drop); + *defer_break = FALSE; + goto IBASE_EXIT; + } + + if (result) + result = string_catn(result, US "\n", 1); + + /* Find the number of fields returned. If this is one, we don't add field + names to the data. Otherwise we do. */ + if (out_sqlda->sqld == 1) + { + if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */ + result = string_catn(result, US buffer, + fetch_field(buffer, sizeof(buffer), + &out_sqlda->sqlvar[0])); + } + + else + for (int i = 0; i < out_sqlda->sqld; i++) + { + int len = fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[i]); + + result = string_catn(result, US out_sqlda->sqlvar[i].aliasname, + out_sqlda->sqlvar[i].aliasname_length); + result = string_catn(result, US "=", 1); + + /* Quote the value if it contains spaces or is empty */ + + if (*out_sqlda->sqlvar[i].sqlind == -1) /* NULL value */ + result = string_catn(result, US "\"\"", 2); + + else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) + { + result = string_catn(result, US "\"", 1); + for (int j = 0; j < len; j++) + { + if (buffer[j] == '\"' || buffer[j] == '\\') + result = string_cat(result, US "\\", 1); + result = string_cat(result, US buffer + j, 1); + } + result = string_catn(result, US "\"", 1); + } + else + result = string_catn(result, US buffer, len); + result = string_catn(result, US " ", 1); + } + } + +/* If result is NULL then no data has been found and so we return FAIL. +Otherwise, we must terminate the string which has been built; string_cat() +always leaves enough room for a terminating zero. */ + +if (!result) + { + yield = FAIL; + *errmsg = US "Interbase: no data found"; + } +else + gstring_release_unused(result); + + +/* Get here by goto from various error checks. */ + +IBASE_EXIT: + +if (stmth) + isc_dsql_free_statement(status, &stmth, DSQL_drop); + +/* Non-NULL result indicates a successful result */ + +if (result) + { + *resultptr = string_from_gstring(result); + return OK; + } +else + { + DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg); + return yield; /* FAIL or DEFER */ + } +} + + + + +/************************************************* +* Find entry point * +*************************************************/ + +/* See local README for interface description. The handle and filename +arguments are not used. Loop through a list of servers while the query is +deferred with a retryable error. */ + +static int +ibase_find(void * handle, const uschar * filename, uschar * query, int length, + uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts) +{ +int sep = 0; +uschar *server; +uschar *list = ibase_servers; + +DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query); + +while ((server = string_nextinlist(&list, &sep, NULL, 0))) + { + BOOL defer_break = FALSE; + int rc = perform_ibase_search(query, server, result, errmsg, &defer_break); + if (rc != DEFER || defer_break) + return rc; + } + +if (!ibase_servers) + *errmsg = US "no Interbase servers defined (ibase_servers option)"; + +return DEFER; +} + + + +/************************************************* +* Quote entry point * +*************************************************/ + +/* The only characters that need to be quoted (with backslash) are newline, +tab, carriage return, backspace, backslash itself, and the quote characters. +Percent, and underscore and not escaped. They are only special in contexts +where they can be wild cards, and this isn't usually the case for data inserted +from messages, since that isn't likely to be treated as a pattern of any kind. +Sadly, MySQL doesn't seem to behave like other programs. If you use something +like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really +can't quote "on spec". + +Arguments: + s the string to be quoted + opt additional option text or NULL if none + idx lookup type index + +Returns: the processed string or NULL for a bad option +*/ + +static uschar * +ibase_quote(uschar * s, uschar * opt, unsigned idx) +{ +int c; +int count = 0; +uschar * t = s, * quoted; + +if (opt) + return NULL; /* No options recognized */ + +while ((c = *t++)) + if (c == '\'') count++; + +t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx); + +while ((c = *s++)) + if (c == '\'') { *t++ = '\''; *t++ = '\''; } + else *t++ = c; + +*t = 0; +return quoted; +} + + +/************************************************* +* Version reporting entry point * +*************************************************/ + +/* See local README for interface description. */ + +#include "../version.h" + +gstring * +ibase_version_report(gstring * g) +{ +#ifdef DYNLOOKUP +g = string_fmt_append(g, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR)); +#endif +return g; +} + + +static lookup_info _lookup_info = { + .name = US"ibase", /* lookup name */ + .type = lookup_querystyle, /* query-style lookup */ + .open = ibase_open, /* open function */ + .check NULL, /* no check function */ + .find = ibase_find, /* find function */ + .close = NULL, /* no close function */ + .tidy = ibase_tidy, /* tidy function */ + .quote = ibase_quote, /* quoting function */ + .version_report = ibase_version_report /* version reporting */ +}; + +#ifdef DYNLOOKUP +#define ibase_lookup_module_info _lookup_module_info +#endif + +static lookup_info *_lookup_list[] = { &_lookup_info }; +lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 }; + +/* End of lookups/ibase.c */ |