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