1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) The Exim Maintainers 2020 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2015 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
10 /* Interface to an Oracle database. This code was originally supplied by
11 Paul Kelly, but I have hacked it around for various reasons, and tried to add
12 some comments from my position of Oracle ignorance. */
18 /* The Oracle system headers */
24 #define PARSE_NO_DEFER 0 /* parse straight away */
25 #define PARSE_V7_LNG 2
26 #define MAX_ITEM_BUFFER_SIZE 1024 /* largest size of a cell of data */
27 #define MAX_SELECT_LIST_SIZE 32 /* maximum number of columns (not rows!) */
29 /* Paul's comment on this was "change this to 512 for 64bit cpu", but I don't
30 understand why. The Oracle manual just asks for 256 bytes.
32 That was years ago. Jin Choi suggested (March 2007) that this change should
33 be made in the source, as at worst it wastes 256 bytes, and it saves people
34 having to discover about this for themselves as more and more systems are
35 64-bit. So I have changed 256 to 512. */
39 /* Internal/external datatype codes */
48 /* ORACLE error codes used in demonstration programs */
50 #define VAR_NOT_IN_LIST 1007
51 #define NO_DATA_FOUND 1403
53 typedef struct Ora_Describe {
56 sb1 buf[MAX_ITEM_BUFFER_SIZE];
64 typedef struct Ora_Define {
65 ub1 buf[MAX_ITEM_BUFFER_SIZE];
69 ub2 col_retlen, col_retcode;
72 /* Structure and anchor for caching connections. */
74 typedef struct oracle_connection {
75 struct oracle_connection *next;
77 struct cda_def *handle;
81 static oracle_connection *oracle_connections = NULL;
87 /*************************************************
88 * Set up message after error *
89 *************************************************/
91 /* Sets up a message from a local string plus whatever Oracle gives.
94 oracle_handle the handle of the connection
96 msg local text message
100 oracle_error(struct cda_def *oracle_handle, int rc, uschar *msg)
103 oerhms(oracle_handle, rc, tmp, sizeof(tmp));
104 return string_sprintf("ORACLE %s: %s", msg, tmp);
109 /*************************************************
110 * Describe and define the select list items *
111 *************************************************/
113 /* Figures out sizes, types, and numbers.
118 desc descriptions put here
120 Returns: number of fields
124 describe_define(Cda_Def *cda, Ora_Define *def, Ora_Describe *desc)
126 sword col, deflen, deftyp;
128 static sword numwidth = 8;
130 /* Describe the select-list items. */
132 for (col = 0; col < MAX_SELECT_LIST_SIZE; col++)
134 desc[col].buflen = MAX_ITEM_BUFFER_SIZE;
136 if (odescr(cda, col + 1, &desc[col].dbsize,
137 &desc[col].dbtype, &desc[col].buf[0],
138 &desc[col].buflen, &desc[col].dsize,
139 &desc[col].precision, &desc[col].scale,
140 &desc[col].nullok) != 0)
142 /* Break on end of select list. */
143 if (cda->rc == VAR_NOT_IN_LIST) break; else return -1;
146 /* Adjust sizes and types for display, handling NUMBER with scale as float. */
148 if (desc[col].dbtype == NUMBER_TYPE)
150 desc[col].dbsize = numwidth;
151 if (desc[col].scale != 0)
153 defptr = (ub1 *)&def[col].flt_buf;
154 deflen = (sword) sizeof(float);
156 desc[col].dbtype = FLOAT_TYPE;
160 defptr = (ub1 *)&def[col].int_buf;
161 deflen = (sword) sizeof(sword);
163 desc[col].dbtype = INT_TYPE;
168 if (desc[col].dbtype == DATE_TYPE)
169 desc[col].dbsize = 9;
170 if (desc[col].dbtype == ROWID_TYPE)
171 desc[col].dbsize = 18;
172 defptr = def[col].buf;
173 deflen = desc[col].dbsize > MAX_ITEM_BUFFER_SIZE ?
174 MAX_ITEM_BUFFER_SIZE : desc[col].dbsize + 1;
175 deftyp = STRING_TYPE;
176 desc[col].dbtype = STRING_TYPE;
179 /* Define an output variable */
181 if (odefin(cda, col + 1,
182 defptr, deflen, deftyp,
183 -1, &def[col].indp, (text *) 0, -1, -1,
184 &def[col].col_retlen,
185 &def[col].col_retcode) != 0)
187 } /* Loop for each column */
194 /*************************************************
196 *************************************************/
198 /* See local README for interface description. */
201 oracle_open(const uschar * filename, uschar ** errmsg)
203 return (void *)(1); /* Just return something non-null */
208 /*************************************************
210 *************************************************/
212 /* See local README for interface description. */
217 oracle_connection *cn;
218 while ((cn = oracle_connections) != NULL)
220 oracle_connections = cn->next;
221 DEBUG(D_lookup) debug_printf_indent("close ORACLE connection: %s\n", cn->server);
228 /*************************************************
229 * Internal search function *
230 *************************************************/
232 /* This function is called from the find entry point to do the search for a
236 query the query string
237 server the server string
238 resultptr where to store the result
239 errmsg where to point an error message
240 defer_break TRUE if no more servers are to be tried after DEFER
242 The server string is of the form "host/dbname/user/password", for compatibility
243 with MySQL and pgsql, but at present, the dbname is not used. This string is in
244 a nextinlist temporary buffer, so can be overwritten.
246 Returns: OK, FAIL, or DEFER
250 perform_oracle_search(uschar *query, uschar *server, uschar **resultptr,
251 uschar **errmsg, BOOL *defer_break)
254 struct cda_def *oracle_handle = NULL;
255 Ora_Describe *desc = NULL;
256 Ora_Define *def = NULL;
260 unsigned int num_fields = 0;
261 gstring * result = NULL;
262 oracle_connection *cn = NULL;
263 uschar *server_copy = NULL;
266 /* Disaggregate the parameters from the server argument. The order is host,
267 database, user, password. We can write to the string, since it is in a
268 nextinlist temporary buffer. The copy of the string that is used for caching
269 has the password removed. This copy is also used for debugging output. */
271 for (int i = 3; i > 0; i--)
273 uschar *pp = Ustrrchr(server, '/');
276 *errmsg = string_sprintf("incomplete ORACLE server data: %s", server);
282 if (i == 3) server_copy = string_copy(server); /* sans password */
284 sdata[0] = server; /* What's left at the start */
286 /* If the database is the empty string, set it NULL - the query must then
289 if (sdata[1][0] == 0) sdata[1] = NULL;
291 /* See if we have a cached connection to the server */
293 for (cn = oracle_connections; cn; cn = cn->next)
294 if (strcmp(cn->server, server_copy) == 0)
296 oracle_handle = cn->handle;
301 /* If no cached connection, we must set one up */
305 DEBUG(D_lookup) debug_printf_indent("ORACLE new connection: host=%s database=%s "
306 "user=%s\n", sdata[0], sdata[1], sdata[2]);
308 /* Get store for a new connection, initialize it, and connect to the server */
310 oracle_handle = store_get(sizeof(struct cda_def), GET_UNTAINTED);
311 hda = store_get(HDA_SIZE, GET_UNTAINTED);
312 memset(hda,'\0',HDA_SIZE);
315 * Perform a default (blocking) login
317 * sdata[0] = tnsname (service name - typically host name)
318 * sdata[1] = dbname - not used at present
319 * sdata[2] = username
323 if(olog(oracle_handle, hda, sdata[2], -1, sdata[3], -1, sdata[0], -1,
324 (ub4)OCI_LM_DEF) != 0)
326 *errmsg = oracle_error(oracle_handle, oracle_handle->rc,
327 US"connection failed");
328 *defer_break = FALSE;
329 goto ORACLE_EXIT_NO_VALS;
332 /* Add the connection to the cache */
334 cn = store_get(sizeof(oracle_connection), GET_UNTAINTED);
335 cn->server = server_copy;
336 cn->handle = oracle_handle;
337 cn->next = oracle_connections;
339 oracle_connections = cn;
342 /* Else use a previously cached connection - we can write to the server string
343 to obliterate the password because it is in a nextinlist temporary buffer. */
348 debug_printf_indent("ORACLE using cached connection for %s\n", server_copy);
351 /* We have a connection. Open a cursor and run the query */
353 cda = store_get(sizeof(Cda_Def), GET_UNTAINTED);
355 if (oopen(cda, oracle_handle, (text *)0, -1, -1, (text *)0, -1) != 0)
357 *errmsg = oracle_error(oracle_handle, cda->rc, "failed to open cursor");
358 *defer_break = FALSE;
359 goto ORACLE_EXIT_NO_VALS;
362 if (oparse(cda, (text *)query, (sb4) -1,
363 (sword)PARSE_NO_DEFER, (ub4)PARSE_V7_LNG) != 0)
365 *errmsg = oracle_error(oracle_handle, cda->rc, "query failed");
366 *defer_break = FALSE;
368 goto ORACLE_EXIT_NO_VALS;
371 /* Find the number of fields returned and sort out their types. If the number
372 is one, we don't add field names to the data. Otherwise we do. */
374 def = store_get(sizeof(Ora_Define)*MAX_SELECT_LIST_SIZE, GET_UNTAINTED);
375 desc = store_get(sizeof(Ora_Describe)*MAX_SELECT_LIST_SIZE, GET_UNTAINTED);
377 if ((num_fields = describe_define(cda,def,desc)) == -1)
379 *errmsg = oracle_error(oracle_handle, cda->rc, "describe_define failed");
380 *defer_break = FALSE;
386 *errmsg = oracle_error(oracle_handle, cda->rc, "oexec failed");
387 *defer_break = FALSE;
391 /* Get the fields and construct the result string. If there is more than one
392 row, we insert '\n' between them. */
394 while (cda->rc != NO_DATA_FOUND) /* Loop for each row */
397 if(cda->rc == NO_DATA_FOUND) break;
399 if (result) result = string_catn(result, "\n", 1);
401 /* Single field - just add on the data */
404 result = string_catn(result, def[0].buf, def[0].col_retlen);
406 /* Multiple fields - precede by file name, removing {lead,trail}ing WS */
408 else for (int i = 0; i < num_fields; i++)
411 uschar * s = US desc[i].buf;
413 Uskip_whitespace(&s);
415 while (slen > 0 && isspace(s[slen-1])) slen--;
416 result = string_catn(result, s, slen);
417 result = string_catn(result, US"=", 1);
419 /* int and float type won't ever need escaping. Otherwise, quote the value
420 if it contains spaces or is empty. */
422 if (desc[i].dbtype != INT_TYPE && desc[i].dbtype != FLOAT_TYPE &&
423 (def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
425 result = string_catn(result, "\"", 1);
426 for (int j = 0; j < def[i].col_retlen; j++)
428 if (def[i].buf[j] == '\"' || def[i].buf[j] == '\\')
429 result = string_catn(result, "\\", 1);
430 result = string_catn(result, def[i].buf+j, 1);
432 result = string_catn(result, "\"", 1);
435 else switch(desc[i].dbtype)
438 result = string_cat(result, string_sprintf("%d", def[i].int_buf));
442 result = string_cat(result, string_sprintf("%f", def[i].flt_buf));
446 result = string_catn(result, def[i].buf, def[i].col_retlen);
450 *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
451 *defer_break = FALSE;
456 result = string_catn(result, " ", 1);
460 /* If result is NULL then no data has been found and so we return FAIL.
461 Otherwise, we must terminate the string which has been built; string_cat()
462 always leaves enough room for a terminating zero. */
467 *errmsg = "ORACLE: no data found";
470 gstring_release_unused(result);
472 /* Get here by goto from various error checks. */
476 /* Close the cursor; don't close the connection, as it is cached. */
482 /* Non-NULL result indicates a successful result */
486 *resultptr = string_from_gstring(result);
491 DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
492 return yield; /* FAIL or DEFER */
499 /*************************************************
501 *************************************************/
503 /* See local README for interface description. The handle and filename
504 arguments are not used. Loop through a list of servers while the query is
505 deferred with a retryable error. */
508 oracle_find(void * handle, const uschar * filename, uschar * query, int length,
509 uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts)
513 uschar *list = oracle_servers;
515 do_cache = do_cache; /* Placate picky compilers */
517 DEBUG(D_lookup) debug_printf_indent("ORACLE query: %s\n", query);
519 while ((server = string_nextinlist(&list, &sep, NULL, 0)))
522 int rc = perform_oracle_search(query, server, result, errmsg, &defer_break);
523 if (rc != DEFER || defer_break) return rc;
527 *errmsg = "no ORACLE servers defined (oracle_servers option)";
534 /*************************************************
535 * Quote entry point *
536 *************************************************/
538 /* The only characters that need to be quoted (with backslash) are newline,
539 tab, carriage return, backspace, backslash itself, and the quote characters.
540 Percent and underscore are not escaped. They are only special in contexts where
541 they can be wild cards, and this isn't usually the case for data inserted from
542 messages, since that isn't likely to be treated as a pattern of any kind.
545 s the string to be quoted
546 opt additional option text or NULL if none
547 idx lookup type index
549 Returns: the processed string or NULL for a bad option
553 oracle_quote(uschar * s, uschar * opt, unsigned idx)
556 uschar * t = s, * quoted;
558 if (opt) return NULL; /* No options are recognized */
561 if (strchr("\n\t\r\b\'\"\\", c) != NULL) count++;
563 t = quoted = store_get_quoted((int)Ustrlen(s) + count + 1, s, idx);
567 if (strchr("\n\t\r\b\'\"\\", c) != NULL)
572 case '\n': *t++ = 'n';
574 case '\t': *t++ = 't';
576 case '\r': *t++ = 'r';
578 case '\b': *t++ = 'b';
592 /*************************************************
593 * Version reporting entry point *
594 *************************************************/
596 /* See local README for interface description. */
598 #include "../version.h"
601 oracle_version_report(gstring * g)
604 g = string_fmt_append(g, "Library version: Oracle: Exim version %s\n", EXIM_VERSION_STR);
610 static lookup_info _lookup_info = {
611 .name = US"oracle", /* lookup name */
612 .type = lookup_querystyle, /* query-style lookup */
613 .open = oracle_open, /* open function */
614 .check = NULL, /* check function */
615 .find = oracle_find, /* find function */
616 .close = NULL, /* no close function */
617 .tidy = oracle_tidy, /* tidy function */
618 .quote = oracle_quote, /* quoting function */
619 .version_report = oracle_version_report /* version reporting */
623 #define oracle_lookup_module_info _lookup_module_info
626 static lookup_info *_lookup_list[] = { &_lookup_info };
627 lookup_module_info oracle_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
629 /* End of lookups/oracle.c */