Copyright updates:
[exim.git] / src / src / lookups / ibase.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2020 - 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
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"
13
14 #include <ibase.h>              /* The system header */
15
16 /* Structure and anchor for caching connections. */
17
18 typedef 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
25 static ibase_connection *ibase_connections = NULL;
26
27
28
29 /*************************************************
30 *              Open entry point                  *
31 *************************************************/
32
33 /* See local README for interface description. */
34
35 static void *ibase_open(const uschar * filename, uschar ** errmsg)
36 {
37 return (void *) (1);        /* Just return something non-null */
38 }
39
40
41
42 /*************************************************
43 *               Tidy entry point                 *
44 *************************************************/
45
46 /* See local README for interface description. */
47
48 static void ibase_tidy(void)
49 {
50     ibase_connection *cn;
51     ISC_STATUS status[20];
52
53     while ((cn = ibase_connections) != NULL) {
54         ibase_connections = cn->next;
55         DEBUG(D_lookup) debug_printf_indent("close Interbase connection: %s\n",
56                                      cn->server);
57         isc_commit_transaction(status, &cn->transh);
58         isc_detach_database(status, &cn->dbh);
59     }
60 }
61
62 static 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
93 single server.
94
95 Arguments:
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
102 The server string is of the form "host:dbname|user|password". The host can be
103 host:port. This string is in a nextinlist temporary buffer, so can be
104 overwritten.
105
106 Returns:       OK, FAIL, or DEFER
107 */
108
109 static int
110 perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
111                      uschar ** errmsg, BOOL * defer_break)
112 {
113 isc_stmt_handle stmth = NULL;
114 XSQLDA *out_sqlda;
115 XSQLVAR *var;
116 int i;
117 rmark reset_point;
118
119 char buffer[256];
120 ISC_STATUS status[20], *statusp = status;
121
122 gstring * result;
123 int yield = DEFER;
124 ibase_connection *cn;
125 uschar *server_copy = NULL;
126 uschar *sdata[3];
127
128 /* Disaggregate the parameters from the server argument. The order is host,
129 database, user, password. We can write to the string, since it is in a
130 nextinlist temporary buffer. The copy of the string that is used for caching
131 has the password removed. This copy is also used for debugging output. */
132
133 for (int i = 2; i > 0; i--)
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;
143     }
144   *pp++ = 0;
145   sdata[i] = pp;
146   if (i == 2)
147       server_copy = string_copy(server);   /* sans password */
148   }
149 sdata[0] = server;          /* What's left at the start */
150
151 /* See if we have a cached connection to the server */
152
153 for (cn = ibase_connections; cn != NULL; cn = cn->next)
154   if (Ustrcmp(cn->server, server_copy) == 0)
155     break;
156
157 /* Use a previously cached connection ? */
158
159 if (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);
173     }
174   else
175     DEBUG(D_lookup) debug_printf_indent("Interbase using cached connection for %s\n",
176                      server_copy);
177   }
178 else
179   {
180   cn = store_get(sizeof(ibase_connection), GET_UNTAINTED);
181   cn->server = server_copy;
182   cn->dbh = NULL;
183   cn->transh = NULL;
184   cn->next = ibase_connections;
185   ibase_connections = cn;
186   }
187
188 /* If no cached connection, we must set one up. */
189
190 if (cn->dbh == NULL || cn->transh == NULL)
191   {
192   char *dpb;
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]);
204   for (char * p = sdata[1]; *p;)
205       *dpb++ = *p++;
206   *dpb++ = isc_dpb_password;
207   *dpb++ = strlen(sdata[2]);
208   for (char * p = sdata[2]; *p;)
209       *dpb++ = *p++;
210   dpb_length = dpb - buffer;
211
212   DEBUG(D_lookup)
213       debug_printf_indent("new Interbase connection: database=%s user=%s\n",
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;
225     }
226
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;
239     }
240   }
241
242 /* Run the query */
243 if (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
253 /* Lacking any information, assume that the data is untainted */
254 reset_point = store_mark();
255 out_sqlda = store_get(XSQLDA_LENGTH(1), GET_UNTAINTED);
256 out_sqlda->version = SQLDA_VERSION1;
257 out_sqlda->sqln = 1;
258
259 if (isc_dsql_prepare
260     (status, &cn->transh, &stmth, 0, query, 1, out_sqlda))
261   {
262   isc_interprete(buffer, &statusp);
263   reset_point = store_reset(reset_point);
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   }
271
272 /* re-allocate the output structure if there's more than one field */
273 if (out_sqlda->sqln < out_sqlda->sqld)
274   {
275   XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld), GET_UNTAINTED);
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);
281     reset_point = store_reset(reset_point);
282     out_sqlda = NULL;
283     *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
284                        buffer);
285     *defer_break = FALSE;
286     goto IBASE_EXIT;
287     }
288   out_sqlda = new_sqlda;
289   }
290
291 /* allocate storage for every returned field */
292 for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
293   {
294   switch (var->sqltype & ~1)
295     {
296     case SQL_VARYING:
297         var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2, GET_UNTAINTED);
298         break;
299     case SQL_TEXT:
300         var->sqldata = CS store_get(sizeof(char) * var->sqllen, GET_UNTAINTED);
301         break;
302     case SQL_SHORT:
303         var->sqldata = CS  store_get(sizeof(short), GET_UNTAINTED);
304         break;
305     case SQL_LONG:
306         var->sqldata = CS  store_get(sizeof(ISC_LONG), GET_UNTAINTED);
307         break;
308 #ifdef SQL_INT64
309     case SQL_INT64:
310         var->sqldata = CS  store_get(sizeof(ISC_INT64), GET_UNTAINTED);
311         break;
312 #endif
313     case SQL_FLOAT:
314         var->sqldata = CS  store_get(sizeof(float), GET_UNTAINTED);
315         break;
316     case SQL_DOUBLE:
317         var->sqldata = CS  store_get(sizeof(double), GET_UNTAINTED);
318         break;
319 #ifdef SQL_TIMESTAMP
320     case SQL_DATE:
321         var->sqldata = CS  store_get(sizeof(ISC_QUAD), GET_UNTAINTED);
322         break;
323 #else
324     case SQL_TIMESTAMP:
325         var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP), GET_UNTAINTED);
326         break;
327     case SQL_TYPE_DATE:
328         var->sqldata = CS  store_get(sizeof(ISC_DATE), GET_UNTAINTED);
329         break;
330     case SQL_TYPE_TIME:
331         var->sqldata = CS  store_get(sizeof(ISC_TIME), GET_UNTAINTED);
332         break;
333   #endif
334     }
335   if (var->sqltype & 1)
336     var->sqlind = (short *) store_get(sizeof(short), GET_UNTAINTED);
337   }
338
339 /* finally, we're ready to execute the statement */
340 if (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
351 while (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;
362     }
363
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]));
375     }
376
377   else
378     for (int i = 0; i < out_sqlda->sqld; i++)
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         {
393         result = string_catn(result, US "\"", 1);
394         for (int j = 0; j < len; j++)
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
408 /* If result is NULL then no data has been found and so we return FAIL.
409 Otherwise, we must terminate the string which has been built; string_cat()
410 always leaves enough room for a terminating zero. */
411
412 if (!result)
413   {
414   yield = FAIL;
415   *errmsg = US "Interbase: no data found";
416   }
417 else
418   gstring_release_unused(result);
419
420
421 /* Get here by goto from various error checks. */
422
423 IBASE_EXIT:
424
425 if (stmth)
426   isc_dsql_free_statement(status, &stmth, DSQL_drop);
427
428 /* Non-NULL result indicates a successful result */
429
430 if (result)
431   {
432   *resultptr = string_from_gstring(result);
433   return OK;
434   }
435 else
436   {
437   DEBUG(D_lookup) debug_printf_indent("%s\n", *errmsg);
438   return yield;           /* FAIL or DEFER */
439   }
440 }
441
442
443
444
445 /*************************************************
446 *               Find entry point                 *
447 *************************************************/
448
449 /* See local README for interface description. The handle and filename
450 arguments are not used. Loop through a list of servers while the query is
451 deferred with a retryable error. */
452
453 static int
454 ibase_find(void * handle, const uschar * filename, uschar * query, int length,
455   uschar ** result, uschar ** errmsg, uint * do_cache, const uschar * opts)
456 {
457 int sep = 0;
458 uschar *server;
459 uschar *list = ibase_servers;
460
461 DEBUG(D_lookup) debug_printf_indent("Interbase query: %s\n", query);
462
463 while ((server = string_nextinlist(&list, &sep, NULL, 0)))
464   {
465   BOOL defer_break = FALSE;
466   int rc = perform_ibase_search(query, server, result, errmsg, &defer_break);
467   if (rc != DEFER || defer_break)
468     return rc;
469   }
470
471 if (!ibase_servers)
472   *errmsg = US "no Interbase servers defined (ibase_servers option)";
473
474 return DEFER;
475 }
476
477
478
479 /*************************************************
480 *               Quote entry point                *
481 *************************************************/
482
483 /* The only characters that need to be quoted (with backslash) are newline,
484 tab, carriage return, backspace, backslash itself, and the quote characters.
485 Percent, and underscore and not escaped. They are only special in contexts
486 where they can be wild cards, and this isn't usually the case for data inserted
487 from messages, since that isn't likely to be treated as a pattern of any kind.
488 Sadly, MySQL doesn't seem to behave like other programs. If you use something
489 like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
490 can't quote "on spec".
491
492 Arguments:
493   s          the string to be quoted
494   opt        additional option text or NULL if none
495   idx        lookup type index
496
497 Returns:     the processed string or NULL for a bad option
498 */
499
500 static uschar *
501 ibase_quote(uschar * s, uschar * opt, unsigned idx)
502 {
503 int c;
504 int count = 0;
505 uschar * t = s, * quoted;
506
507 if (opt)
508   return NULL;            /* No options recognized */
509
510 while ((c = *t++))
511   if (c == '\'') count++;
512
513 t = quoted = store_get_quoted(Ustrlen(s) + count + 1, s, idx);
514
515 while ((c = *s++))
516   if (c == '\'') { *t++ = '\''; *t++ = '\''; }
517   else *t++ = c;
518
519 *t = 0;
520 return quoted;
521 }
522
523
524 /*************************************************
525 *         Version reporting entry point          *
526 *************************************************/
527
528 /* See local README for interface description. */
529
530 #include "../version.h"
531
532 gstring *
533 ibase_version_report(gstring * g)
534 {
535 #ifdef DYNLOOKUP
536 g = string_fmt_append(g, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR));
537 #endif
538 return g;
539 }
540
541
542 static lookup_info _lookup_info = {
543   .name = US"ibase",                    /* lookup name */
544   .type = lookup_querystyle,            /* query-style lookup */
545   .open = ibase_open,                   /* open function */
546   .check NULL,                          /* no check function */
547   .find = ibase_find,                   /* find function */
548   .close = NULL,                        /* no close function */
549   .tidy = ibase_tidy,                   /* tidy function */
550   .quote = ibase_quote,                 /* quoting function */
551   .version_report = ibase_version_report           /* version reporting */
552 };
553
554 #ifdef DYNLOOKUP
555 #define ibase_lookup_module_info _lookup_module_info
556 #endif
557
558 static lookup_info *_lookup_list[] = { &_lookup_info };
559 lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
560
561 /* End of lookups/ibase.c */