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