1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4 
5 /* Copyright (c) University of Cambridge 1995 - 2018 */
6 /* Copyright (c) The Exim Maintainers 2020 */
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 
ibase_open(const uschar * filename,uschar ** errmsg)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 
ibase_tidy(void)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 
fetch_field(char * buffer,int buffer_size,XSQLVAR * var)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
perform_ibase_search(uschar * query,uschar * server,uschar ** resultptr,uschar ** errmsg,BOOL * defer_break)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), FALSE);
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), FALSE);
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), FALSE);
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, FALSE);
298 	break;
299     case SQL_TEXT:
300 	var->sqldata = CS store_get(sizeof(char) * var->sqllen, FALSE);
301 	break;
302     case SQL_SHORT:
303 	var->sqldata = CS  store_get(sizeof(short), FALSE);
304 	break;
305     case SQL_LONG:
306 	var->sqldata = CS  store_get(sizeof(ISC_LONG), FALSE);
307 	break;
308 #ifdef SQL_INT64
309     case SQL_INT64:
310 	var->sqldata = CS  store_get(sizeof(ISC_INT64), FALSE);
311 	break;
312 #endif
313     case SQL_FLOAT:
314 	var->sqldata = CS  store_get(sizeof(float), FALSE);
315 	break;
316     case SQL_DOUBLE:
317 	var->sqldata = CS  store_get(sizeof(double), FALSE);
318 	break;
319 #ifdef SQL_TIMESTAMP
320     case SQL_DATE:
321 	var->sqldata = CS  store_get(sizeof(ISC_QUAD), FALSE);
322 	break;
323 #else
324     case SQL_TIMESTAMP:
325 	var->sqldata = CS  store_get(sizeof(ISC_TIMESTAMP), FALSE);
326 	break;
327     case SQL_TYPE_DATE:
328 	var->sqldata = CS  store_get(sizeof(ISC_DATE), FALSE);
329 	break;
330     case SQL_TYPE_TIME:
331 	var->sqldata = CS  store_get(sizeof(ISC_TIME), FALSE);
332 	break;
333   #endif
334     }
335   if (var->sqltype & 1)
336     var->sqlind = (short *) store_get(sizeof(short), FALSE);
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
ibase_find(void * handle,const uschar * filename,uschar * query,int length,uschar ** result,uschar ** errmsg,uint * do_cache,const uschar * opts)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 
496 Returns:     the processed string or NULL for a bad option
497 */
498 
ibase_quote(uschar * s,uschar * opt)499 static uschar *ibase_quote(uschar * s, uschar * opt)
500 {
501     register int c;
502     int count = 0;
503     uschar *t = s;
504     uschar *quoted;
505 
506     if (opt != NULL)
507         return NULL;            /* No options recognized */
508 
509     while ((c = *t++) != 0)
510         if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
511             count++;
512 
513     if (count == 0)
514         return s;
515     t = quoted = store_get(Ustrlen(s) + count + 1, FALSE);
516 
517     while ((c = *s++) != 0) {
518         if (Ustrchr("'", c) != NULL) {
519             *t++ = '\'';
520             *t++ = '\'';
521 /*    switch(c)
522       {
523       case '\n': *t++ = 'n';
524       break;
525       case '\t': *t++ = 't';
526       break;
527       case '\r': *t++ = 'r';
528       break;
529       case '\b': *t++ = 'b';
530       break;
531       default:   *t++ = c;
532       break;
533       }*/
534         } else
535             *t++ = c;
536     }
537 
538     *t = 0;
539     return quoted;
540 }
541 
542 
543 /*************************************************
544 *         Version reporting entry point          *
545 *************************************************/
546 
547 /* See local README for interface description. */
548 
549 #include "../version.h"
550 
551 void
ibase_version_report(FILE * f)552 ibase_version_report(FILE *f)
553 {
554 #ifdef DYNLOOKUP
555 fprintf(f, "Library version: ibase: Exim version %s\n", EXIM_VERSION_STR);
556 #endif
557 }
558 
559 
560 static lookup_info _lookup_info = {
561   .name = US"ibase",			/* lookup name */
562   .type = lookup_querystyle,		/* query-style lookup */
563   .open = ibase_open,			/* open function */
564   .check NULL,				/* no check function */
565   .find = ibase_find,			/* find function */
566   .close = NULL,			/* no close function */
567   .tidy = ibase_tidy,			/* tidy function */
568   .quote = ibase_quote,			/* quoting function */
569   .version_report = ibase_version_report           /* version reporting */
570 };
571 
572 #ifdef DYNLOOKUP
573 #define ibase_lookup_module_info _lookup_module_info
574 #endif
575 
576 static lookup_info *_lookup_list[] = { &_lookup_info };
577 lookup_module_info ibase_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
578 
579 /* End of lookups/ibase.c */
580