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