debian experimental exim-daemon-heavy config
[exim.git] / src / src / lookups / ibase.c
CommitLineData
0756eb3c
PH
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
1e1ddfac 6/* Copyright (c) The Exim Maintainers 2020 */
0756eb3c
PH
7/* See the file NOTICE for conditions of use and distribution. */
8
9/* The code in this module was contributed by Ard Biesheuvel. */
10
11#include "../exim.h"
12#include "lf_functions.h"
0756eb3c 13
0756eb3c
PH
14#include <ibase.h> /* The system header */
15
16/* Structure and anchor for caching connections. */
17
18typedef struct ibase_connection {
19 struct ibase_connection *next;
20 uschar *server;
21 isc_db_handle dbh;
22 isc_tr_handle transh;
23} ibase_connection;
24
25static ibase_connection *ibase_connections = NULL;
26
27
28
29/*************************************************
30* Open entry point *
31*************************************************/
32
33/* See local README for interface description. */
34
d447dbd1 35static void *ibase_open(const uschar * filename, uschar ** errmsg)
0756eb3c 36{
d447dbd1 37return (void *) (1); /* Just return something non-null */
0756eb3c
PH
38}
39
40
41
42/*************************************************
43* Tidy entry point *
44*************************************************/
45
46/* See local README for interface description. */
47
e6d225ae 48static void ibase_tidy(void)
0756eb3c
PH
49{
50 ibase_connection *cn;
51 ISC_STATUS status[20];
52
53 while ((cn = ibase_connections) != NULL) {
54 ibase_connections = cn->next;
42c7f0b4 55 DEBUG(D_lookup) debug_printf_indent("close Interbase connection: %s\n",
0756eb3c
PH
56 cn->server);
57 isc_commit_transaction(status, &cn->transh);
58 isc_detach_database(status, &cn->dbh);
59 }
60}
61
62static int fetch_field(char *buffer, int buffer_size, XSQLVAR * var)
63{
64 if (buffer_size < var->sqllen)
65 return 0;
66
67 switch (var->sqltype & ~1) {
68 case SQL_VARYING:
69 strncpy(buffer, &var->sqldata[2], *(short *) var->sqldata);
70 return *(short *) var->sqldata;
71 case SQL_TEXT:
72 strncpy(buffer, var->sqldata, var->sqllen);
73 return var->sqllen;
74 case SQL_SHORT:
75 return sprintf(buffer, "%d", *(short *) var->sqldata);
76 case SQL_LONG:
77 return sprintf(buffer, "%ld", *(ISC_LONG *) var->sqldata);
78#ifdef SQL_INT64
79 case SQL_INT64:
80 return sprintf(buffer, "%lld", *(ISC_INT64 *) var->sqldata);
81#endif
82 default:
83 /* not implemented */
84 return 0;
85 }
86}
87
88/*************************************************
89* Internal search function *
90*************************************************/
91
92/* This function is called from the find entry point to do the search for a
93single server.
94
95Arguments:
96 query the query string
97 server the server string
98 resultptr where to store the result
99 errmsg where to point an error message
100 defer_break TRUE if no more servers are to be tried after DEFER
101
102The server string is of the form "host:dbname|user|password". The host can be
103host:port. This string is in a nextinlist temporary buffer, so can be
104overwritten.
105
106Returns: OK, FAIL, or DEFER
107*/
108
109static int
110perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
111 uschar ** errmsg, BOOL * defer_break)
112{
acec9514
JH
113isc_stmt_handle stmth = NULL;
114XSQLDA *out_sqlda;
115XSQLVAR *var;
d7978c0f 116int i;
f3ebb786 117rmark reset_point;
acec9514
JH
118
119char buffer[256];
120ISC_STATUS status[20], *statusp = status;
121
122gstring * result;
acec9514
JH
123int yield = DEFER;
124ibase_connection *cn;
125uschar *server_copy = NULL;
126uschar *sdata[3];
0756eb3c
PH
127
128/* Disaggregate the parameters from the server argument. The order is host,
129database, user, password. We can write to the string, since it is in a
130nextinlist temporary buffer. The copy of the string that is used for caching
131has the password removed. This copy is also used for debugging output. */
132
d7978c0f 133for (int i = 2; i > 0; i--)
acec9514
JH
134 {
135 uschar *pp = Ustrrchr(server, '|');
136
137 if (pp == NULL)
138 {
139 *errmsg = string_sprintf("incomplete Interbase server data: %s",
140 (i == 3) ? server : server_copy);
141 *defer_break = TRUE;
142 return DEFER;
0756eb3c 143 }
acec9514
JH
144 *pp++ = 0;
145 sdata[i] = pp;
146 if (i == 2)
147 server_copy = string_copy(server); /* sans password */
148 }
149sdata[0] = server; /* What's left at the start */
0756eb3c
PH
150
151/* See if we have a cached connection to the server */
152
acec9514
JH
153for (cn = ibase_connections; cn != NULL; cn = cn->next)
154 if (Ustrcmp(cn->server, server_copy) == 0)
155 break;
0756eb3c
PH
156
157/* Use a previously cached connection ? */
158
acec9514
JH
159if (cn)
160 {
161 static char db_info_options[] = { isc_info_base_level };
162
163 /* test if the connection is alive */
164 if (isc_database_info(status, &cn->dbh, sizeof(db_info_options),
165 db_info_options, sizeof(buffer), buffer))
166 {
167 /* error occurred: assume connection is down */
168 DEBUG(D_lookup)
169 debug_printf
170 ("Interbase cleaning up cached connection: %s\n",
171 cn->server);
172 isc_detach_database(status, &cn->dbh);
0756eb3c 173 }
acec9514 174 else
42c7f0b4 175 DEBUG(D_lookup) debug_printf_indent("Interbase using cached connection for %s\n",
acec9514 176 server_copy);
acec9514
JH
177 }
178else
179 {
f3ebb786 180 cn = store_get(sizeof(ibase_connection), FALSE);
acec9514
JH
181 cn->server = server_copy;
182 cn->dbh = NULL;
183 cn->transh = NULL;
184 cn->next = ibase_connections;
185 ibase_connections = cn;
186 }
0756eb3c
PH
187
188/* If no cached connection, we must set one up. */
189
acec9514
JH
190if (cn->dbh == NULL || cn->transh == NULL)
191 {
d7978c0f 192 char *dpb;
acec9514
JH
193 short dpb_length;
194 static char trans_options[] =
195 { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
196 isc_tpb_rec_version
197 };
198
199 /* Construct the database parameter buffer. */
200 dpb = buffer;
201 *dpb++ = isc_dpb_version1;
202 *dpb++ = isc_dpb_user_name;
203 *dpb++ = strlen(sdata[1]);
d7978c0f 204 for (char * p = sdata[1]; *p;)
acec9514
JH
205 *dpb++ = *p++;
206 *dpb++ = isc_dpb_password;
207 *dpb++ = strlen(sdata[2]);
d7978c0f 208 for (char * p = sdata[2]; *p;)
acec9514
JH
209 *dpb++ = *p++;
210 dpb_length = dpb - buffer;
211
212 DEBUG(D_lookup)
42c7f0b4 213 debug_printf_indent("new Interbase connection: database=%s user=%s\n",
acec9514
JH
214 sdata[0], sdata[1]);
215
216 /* Connect to the database */
217 if (isc_attach_database
218 (status, 0, sdata[0], &cn->dbh, dpb_length, buffer))
219 {
220 isc_interprete(buffer, &statusp);
221 *errmsg =
222 string_sprintf("Interbase attach() failed: %s", buffer);
223 *defer_break = FALSE;
224 goto IBASE_EXIT;
0756eb3c
PH
225 }
226
acec9514
JH
227 /* Now start a read-only read-committed transaction */
228 if (isc_start_transaction
229 (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
230 trans_options))
231 {
232 isc_interprete(buffer, &statusp);
233 isc_detach_database(status, &cn->dbh);
234 *errmsg =
235 string_sprintf("Interbase start_transaction() failed: %s",
236 buffer);
237 *defer_break = FALSE;
238 goto IBASE_EXIT;
0756eb3c 239 }
acec9514 240 }
0756eb3c 241
acec9514
JH
242/* Run the query */
243if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth))
244 {
245 isc_interprete(buffer, &statusp);
246 *errmsg =
247 string_sprintf("Interbase alloc_statement() failed: %s",
248 buffer);
249 *defer_break = FALSE;
250 goto IBASE_EXIT;
251 }
252
f3ebb786
JH
253/* Lacking any information, assume that the data is untainted */
254reset_point = store_mark();
255out_sqlda = store_get(XSQLDA_LENGTH(1), FALSE);
acec9514
JH
256out_sqlda->version = SQLDA_VERSION1;
257out_sqlda->sqln = 1;
258
259if (isc_dsql_prepare
260 (status, &cn->transh, &stmth, 0, query, 1, out_sqlda))
261 {
262 isc_interprete(buffer, &statusp);
f3ebb786 263 reset_point = store_reset(reset_point);
acec9514
JH
264 out_sqlda = NULL;
265 *errmsg =
266 string_sprintf("Interbase prepare_statement() failed: %s",
267 buffer);
268 *defer_break = FALSE;
269 goto IBASE_EXIT;
270 }
0756eb3c
PH
271
272/* re-allocate the output structure if there's more than one field */
acec9514
JH
273if (out_sqlda->sqln < out_sqlda->sqld)
274 {
f3ebb786 275 XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), FALSE);
acec9514
JH
276 if (isc_dsql_describe
277 (status, &stmth, out_sqlda->version, new_sqlda))
278 {
279 isc_interprete(buffer, &statusp);
280 isc_dsql_free_statement(status, &stmth, DSQL_drop);
f3ebb786 281 reset_point = store_reset(reset_point);
acec9514
JH
282 out_sqlda = NULL;
283 *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
284 buffer);
285 *defer_break = FALSE;
286 goto IBASE_EXIT;
0756eb3c 287 }
acec9514
JH
288 out_sqlda = new_sqlda;
289 }
0756eb3c
PH
290
291/* allocate storage for every returned field */
acec9514
JH
292for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
293 {
294 switch (var->sqltype & ~1)
295 {
296 case SQL_VARYING:
f3ebb786 297 var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, FALSE);
acec9514
JH
298 break;
299 case SQL_TEXT:
f3ebb786 300 var->sqldata = CS store_get(sizeof(char) * var->sqllen, FALSE);
acec9514
JH
301 break;
302 case SQL_SHORT:
f3ebb786 303 var->sqldata = CS store_get(sizeof(short), FALSE);
acec9514
JH
304 break;
305 case SQL_LONG:
f3ebb786 306 var->sqldata = CS store_get(sizeof(ISC_LONG), FALSE);
acec9514 307 break;
0756eb3c 308#ifdef SQL_INT64
acec9514 309 case SQL_INT64:
f3ebb786 310 var->sqldata = CS store_get(sizeof(ISC_INT64), FALSE);
acec9514 311 break;
0756eb3c 312#endif
acec9514 313 case SQL_FLOAT:
f3ebb786 314 var->sqldata = CS store_get(sizeof(float), FALSE);
acec9514
JH
315 break;
316 case SQL_DOUBLE:
f3ebb786 317 var->sqldata = CS store_get(sizeof(double), FALSE);
acec9514 318 break;
0756eb3c 319#ifdef SQL_TIMESTAMP
acec9514 320 case SQL_DATE:
f3ebb786 321 var->sqldata = CS store_get(sizeof(ISC_QUAD), FALSE);
acec9514 322 break;
0756eb3c 323#else
acec9514 324 case SQL_TIMESTAMP:
f3ebb786 325 var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP), FALSE);
acec9514
JH
326 break;
327 case SQL_TYPE_DATE:
f3ebb786 328 var->sqldata = CS store_get(sizeof(ISC_DATE), FALSE);
acec9514
JH
329 break;
330 case SQL_TYPE_TIME:
f3ebb786 331 var->sqldata = CS store_get(sizeof(ISC_TIME), FALSE);
acec9514
JH
332 break;
333 #endif
0756eb3c 334 }
acec9514 335 if (var->sqltype & 1)
f3ebb786 336 var->sqlind = (short *) store_get(sizeof(short), FALSE);
acec9514
JH
337 }
338
339/* finally, we're ready to execute the statement */
340if (isc_dsql_execute
341 (status, &cn->transh, &stmth, out_sqlda->version, NULL))
342 {
343 isc_interprete(buffer, &statusp);
344 *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
345 buffer);
346 isc_dsql_free_statement(status, &stmth, DSQL_drop);
347 *defer_break = FALSE;
348 goto IBASE_EXIT;
349 }
350
351while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) != 100L)
352 {
353 /* check if an error occurred */
354 if (status[0] & status[1])
355 {
356 isc_interprete(buffer, &statusp);
357 *errmsg =
358 string_sprintf("Interbase fetch() failed: %s", buffer);
359 isc_dsql_free_statement(status, &stmth, DSQL_drop);
360 *defer_break = FALSE;
361 goto IBASE_EXIT;
0756eb3c
PH
362 }
363
acec9514
JH
364 if (result)
365 result = string_catn(result, US "\n", 1);
366
367 /* Find the number of fields returned. If this is one, we don't add field
368 names to the data. Otherwise we do. */
369 if (out_sqlda->sqld == 1)
370 {
371 if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */
372 result = string_catn(result, US buffer,
373 fetch_field(buffer, sizeof(buffer),
374 &out_sqlda->sqlvar[0]));
0756eb3c
PH
375 }
376
acec9514 377 else
d7978c0f 378 for (int i = 0; i < out_sqlda->sqld; i++)
acec9514
JH
379 {
380 int len = fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[i]);
381
382 result = string_catn(result, US out_sqlda->sqlvar[i].aliasname,
383 out_sqlda->sqlvar[i].aliasname_length);
384 result = string_catn(result, US "=", 1);
385
386 /* Quote the value if it contains spaces or is empty */
387
388 if (*out_sqlda->sqlvar[i].sqlind == -1) /* NULL value */
389 result = string_catn(result, US "\"\"", 2);
390
391 else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL)
392 {
acec9514 393 result = string_catn(result, US "\"", 1);
d7978c0f 394 for (int j = 0; j < len; j++)
acec9514
JH
395 {
396 if (buffer[j] == '\"' || buffer[j] == '\\')
397 result = string_cat(result, US "\\", 1);
398 result = string_cat(result, US buffer + j, 1);
399 }
400 result = string_catn(result, US "\"", 1);
401 }
402 else
403 result = string_catn(result, US buffer, len);
404 result = string_catn(result, US " ", 1);
405 }
406 }
407
0756eb3c
PH
408/* If result is NULL then no data has been found and so we return FAIL.
409Otherwise, we must terminate the string which has been built; string_cat()
410always leaves enough room for a terminating zero. */
411
acec9514
JH
412if (!result)
413 {
414 yield = FAIL;
415 *errmsg = US "Interbase: no data found";
416 }
417else
f3ebb786 418 gstring_release_unused(result);
0756eb3c
PH
419
420
421/* Get here by goto from various error checks. */
422
acec9514 423IBASE_EXIT:
0756eb3c 424
acec9514
JH
425if (stmth)
426 isc_dsql_free_statement(status, &stmth, DSQL_drop);
0756eb3c 427
4c04137d 428/* Non-NULL result indicates a successful result */
0756eb3c 429
acec9514
JH
430if (result)
431 {
432 *resultptr = string_from_gstring(result);
433 return OK;
434 }
435else
436 {
42c7f0b4 437 DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
acec9514
JH
438 return yield; /* FAIL or DEFER */
439 }
0756eb3c
PH
440}
441
442
443
444
445/*************************************************
446* Find entry point *
447*************************************************/
448
449/* See local README for interface description. The handle and filename
450arguments are not used. Loop through a list of servers while the query is
451deferred with a retryable error. */
452
e6d225ae 453static int
d447dbd1 454ibase_find(void * handle, const uschar * filename, uschar * query, int length,
67a57a5a 455 uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts)
0756eb3c 456{
d447dbd1
JH
457int sep = 0;
458uschar *server;
459uschar *list = ibase_servers;
460uschar buffer[512];
0756eb3c 461
d447dbd1
JH
462/* Keep picky compilers happy */
463do_cache = do_cache;
0756eb3c 464
d447dbd1
JH
465DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query);
466
67a57a5a 467while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
d447dbd1
JH
468 {
469 BOOL defer_break = FALSE;
470 int rc = perform_ibase_search(query, server, result, errmsg, &defer_break);
471 if (rc != DEFER || defer_break)
472 return rc;
473 }
474
475if (!ibase_servers)
476 *errmsg = US "no Interbase servers defined (ibase_servers option)";
477
478return DEFER;
0756eb3c
PH
479}
480
481
482
483/*************************************************
484* Quote entry point *
485*************************************************/
486
487/* The only characters that need to be quoted (with backslash) are newline,
488tab, carriage return, backspace, backslash itself, and the quote characters.
489Percent, and underscore and not escaped. They are only special in contexts
490where they can be wild cards, and this isn't usually the case for data inserted
491from messages, since that isn't likely to be treated as a pattern of any kind.
492Sadly, MySQL doesn't seem to behave like other programs. If you use something
493like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
494can't quote "on spec".
495
496Arguments:
497 s the string to be quoted
498 opt additional option text or NULL if none
499
500Returns: the processed string or NULL for a bad option
501*/
502
e6d225ae 503static uschar *ibase_quote(uschar * s, uschar * opt)
0756eb3c
PH
504{
505 register int c;
506 int count = 0;
507 uschar *t = s;
508 uschar *quoted;
509
510 if (opt != NULL)
511 return NULL; /* No options recognized */
512
513 while ((c = *t++) != 0)
514 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
515 count++;
516
517 if (count == 0)
518 return s;
f3ebb786 519 t = quoted = store_get(Ustrlen(s) + count + 1, FALSE);
0756eb3c
PH
520
521 while ((c = *s++) != 0) {
522 if (Ustrchr("'", c) != NULL) {
523 *t++ = '\'';
524 *t++ = '\'';
525/* switch(c)
526 {
527 case '\n': *t++ = 'n';
528 break;
529 case '\t': *t++ = 't';
530 break;
531 case '\r': *t++ = 'r';
532 break;
533 case '\b': *t++ = 'b';
534 break;
535 default: *t++ = c;
536 break;
537 }*/
538 } else
539 *t++ = c;
540 }
541
542 *t = 0;
543 return quoted;
544}
545
6545de78
PP
546
547/*************************************************
548* Version reporting entry point *
549*************************************************/
550
551/* See local README for interface description. */
552
553#include "../version.h"
554
555void
556ibase_version_report(FILE *f)
557{
558#ifdef DYNLOOKUP
559fprintf(f, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR);
560#endif
561}
562
563
e6d225ae 564static lookup_info _lookup_info = {
9f400174
JH
565 .name = US"ibase", /* lookup name */
566 .type = lookup_querystyle, /* query-style lookup */
567 .open = ibase_open, /* open function */
568 .check NULL, /* no check function */
569 .find = ibase_find, /* find function */
570 .close = NULL, /* no close function */
571 .tidy = ibase_tidy, /* tidy function */
572 .quote = ibase_quote, /* quoting function */
573 .version_report = ibase_version_report /* version reporting */
e6d225ae
DW
574};
575
576#ifdef DYNLOOKUP
577#define ibase_lookup_module_info _lookup_module_info
0756eb3c 578#endif
94431adb 579
e6d225ae
DW
580static lookup_info *_lookup_list[] = { &_lookup_info };
581lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
0756eb3c
PH
582
583/* End of lookups/ibase.c */