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