Merge branch 'debian'
[hcoop/debian/exim4.git] / src / lookups / pgsql.c
CommitLineData
420a0d19
CE
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
2ea97746 5/* Copyright (c) University of Cambridge 1995 - 2018 */
420a0d19
CE
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* Thanks to Petr Cech for contributing the original code for these
9functions. Thanks to Joachim Wieland for the initial patch for the Unix domain
10socket extension. */
11
12#include "../exim.h"
13#include "lf_functions.h"
14
15#include <libpq-fe.h> /* The system header */
16
17/* Structure and anchor for caching connections. */
18
19typedef struct pgsql_connection {
20 struct pgsql_connection *next;
21 uschar *server;
22 PGconn *handle;
23} pgsql_connection;
24
25static pgsql_connection *pgsql_connections = NULL;
26
27
28
29/*************************************************
30* Open entry point *
31*************************************************/
32
33/* See local README for interface description. */
34
35static void *
36pgsql_open(uschar *filename, uschar **errmsg)
37{
38return (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
49static void
50pgsql_tidy(void)
51{
52pgsql_connection *cn;
53while ((cn = pgsql_connections) != NULL)
54 {
55 pgsql_connections = cn->next;
56 DEBUG(D_lookup) debug_printf("close PGSQL connection: %s\n", cn->server);
57 PQfinish(cn->handle);
58 }
59}
60
61
62/*************************************************
63* Notice processor function for pgsql *
64*************************************************/
65
66/* This function is passed to pgsql below, and called for any PostgreSQL
67"notices". By default they are written to stderr, which is undesirable.
68
69Arguments:
70 arg an opaque user cookie (not used)
71 message the notice
72
73Returns: nothing
74*/
75
76static void
77notice_processor(void *arg, const char *message)
78{
79arg = arg; /* Keep compiler happy */
80DEBUG(D_lookup) debug_printf("PGSQL: %s\n", message);
81}
82
83
84
85/*************************************************
86* Internal search function *
87*************************************************/
88
89/* This function is called from the find entry point to do the search for a
90single server. The server string is of the form "server/dbname/user/password".
91
92PostgreSQL supports connections through Unix domain sockets. This is usually
93faster and costs less cpu time than a TCP/IP connection. However it can only be
94used if the mail server runs on the same machine as the database server. A
95configuration line for PostgreSQL via Unix domain sockets looks like this:
96
97hide pgsql_servers = (/tmp/.s.PGSQL.5432)/db/user/password[:<nextserver>]
98
99We enclose the path name in parentheses so that its slashes aren't visually
2ea97746 100confused with the delimiters for the other pgsql_server settings.
420a0d19
CE
101
102For TCP/IP connections, the server is a host name and optional port (with a
103colon separator).
104
105NOTE:
106 1) All three '/' must be present.
107 2) If host is omitted the local unix socket is used.
108
109Arguments:
110 query the query string
111 server the server string; this is in dynamic memory and can be updated
112 resultptr where to store the result
113 errmsg where to point an error message
114 defer_break set TRUE if no more servers are to be tried after DEFER
115 do_cache set FALSE if data is changed
116
117Returns: OK, FAIL, or DEFER
118*/
119
120static int
2ea97746
CE
121perform_pgsql_search(const uschar *query, uschar *server, uschar **resultptr,
122 uschar **errmsg, BOOL *defer_break, uint *do_cache)
420a0d19
CE
123{
124PGconn *pg_conn = NULL;
125PGresult *pg_result = NULL;
126
127int i;
2ea97746 128gstring * result = NULL;
420a0d19
CE
129int yield = DEFER;
130unsigned int num_fields, num_tuples;
420a0d19
CE
131pgsql_connection *cn;
132uschar *server_copy = NULL;
133uschar *sdata[3];
134
135/* Disaggregate the parameters from the server argument. The order is host or
136path, database, user, password. We can write to the string, since it is in a
137nextinlist temporary buffer. The copy of the string that is used for caching
138has the password removed. This copy is also used for debugging output. */
139
140for (i = 2; i >= 0; i--)
141 {
142 uschar *pp = Ustrrchr(server, '/');
2ea97746 143 if (!pp)
420a0d19
CE
144 {
145 *errmsg = string_sprintf("incomplete pgSQL server data: %s",
146 (i == 2)? server : server_copy);
147 *defer_break = TRUE;
148 return DEFER;
149 }
150 *pp++ = 0;
151 sdata[i] = pp;
152 if (i == 2) server_copy = string_copy(server); /* sans password */
153 }
154
155/* The total server string has now been truncated so that what is left at the
156start is the identification of the server (host or path). See if we have a
157cached connection to the server. */
158
2ea97746 159for (cn = pgsql_connections; cn; cn = cn->next)
420a0d19
CE
160 if (Ustrcmp(cn->server, server_copy) == 0)
161 {
162 pg_conn = cn->handle;
163 break;
164 }
420a0d19
CE
165
166/* If there is no cached connection, we must set one up. */
167
2ea97746 168if (!cn)
420a0d19
CE
169 {
170 uschar *port = US"";
171
172 /* For a Unix domain socket connection, the path is in parentheses */
173
174 if (*server == '(')
175 {
176 uschar *last_slash, *last_dot, *p;
177
178 p = ++server;
2ea97746 179 while (*p && *p != ')') p++;
420a0d19
CE
180 *p = 0;
181
182 last_slash = Ustrrchr(server, '/');
183 last_dot = Ustrrchr(server, '.');
184
185 DEBUG(D_lookup) debug_printf("PGSQL new connection: socket=%s "
186 "database=%s user=%s\n", server, sdata[0], sdata[1]);
187
188 /* A valid socket name looks like this: /var/run/postgresql/.s.PGSQL.5432
189 We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
190 argument and put '5432' into the port variable. */
191
2ea97746 192 if (!last_slash || !last_dot)
420a0d19 193 {
2ea97746 194 *errmsg = string_sprintf("PGSQL invalid filename for socket: %s", server);
420a0d19
CE
195 *defer_break = TRUE;
196 return DEFER;
197 }
198
199 /* Terminate the path name and set up the port: we'll have something like
200 server = "/var/run/postgresql" and port = "5432". */
201
202 *last_slash = 0;
203 port = last_dot + 1;
204 }
205
206 /* Host connection; sort out the port */
207
208 else
209 {
210 uschar *p;
2ea97746 211 if ((p = Ustrchr(server, ':')))
420a0d19
CE
212 {
213 *p++ = 0;
214 port = p;
215 }
216
2ea97746 217 if (Ustrchr(server, '/'))
420a0d19
CE
218 {
219 *errmsg = string_sprintf("unexpected slash in pgSQL server hostname: %s",
220 server);
221 *defer_break = TRUE;
222 return DEFER;
223 }
224
225 DEBUG(D_lookup) debug_printf("PGSQL new connection: host=%s port=%s "
226 "database=%s user=%s\n", server, port, sdata[0], sdata[1]);
227 }
228
229 /* If the database is the empty string, set it NULL - the query must then
230 define it. */
231
232 if (sdata[0][0] == 0) sdata[0] = NULL;
233
234 /* Get store for a new handle, initialize it, and connect to the server */
235
236 pg_conn=PQsetdbLogin(
237 /* host port options tty database user passwd */
238 CS server, CS port, NULL, NULL, CS sdata[0], CS sdata[1], CS sdata[2]);
239
240 if(PQstatus(pg_conn) == CONNECTION_BAD)
241 {
242 store_reset(server_copy);
243 *errmsg = string_sprintf("PGSQL connection failed: %s",
244 PQerrorMessage(pg_conn));
245 PQfinish(pg_conn);
246 goto PGSQL_EXIT;
247 }
248
249 /* Set the client encoding to SQL_ASCII, which means that the server will
250 not try to interpret the query as being in any fancy encoding such as UTF-8
251 or other multibyte code that might cause problems with escaping. */
252
253 PQsetClientEncoding(pg_conn, "SQL_ASCII");
254
255 /* Set the notice processor to prevent notices from being written to stderr
256 (which is what the default does). Our function (above) just produces debug
257 output. */
258
259 PQsetNoticeProcessor(pg_conn, notice_processor, NULL);
260
261 /* Add the connection to the cache */
262
263 cn = store_get(sizeof(pgsql_connection));
264 cn->server = server_copy;
265 cn->handle = pg_conn;
266 cn->next = pgsql_connections;
267 pgsql_connections = cn;
268 }
269
270/* Else use a previously cached connection */
271
272else
273 {
274 DEBUG(D_lookup) debug_printf("PGSQL using cached connection for %s\n",
275 server_copy);
276 }
277
278/* Run the query */
279
2ea97746
CE
280pg_result = PQexec(pg_conn, CS query);
281switch(PQresultStatus(pg_result))
282 {
283 case PGRES_EMPTY_QUERY:
284 case PGRES_COMMAND_OK:
420a0d19 285 /* The command was successful but did not return any data since it was
2ea97746
CE
286 not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
287 high level code to not cache this query, and clean the current cache for
288 this handle by setting *do_cache zero. */
289
290 result = string_cat(result, US PQcmdTuples(pg_result));
291 *do_cache = 0;
420a0d19 292 DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
2ea97746
CE
293 "but was successful. Rows affected: %s\n", string_from_gstring(result));
294 break;
420a0d19 295
2ea97746 296 case PGRES_TUPLES_OK:
420a0d19
CE
297 break;
298
2ea97746 299 default:
420a0d19
CE
300 /* This was the original code:
301 *errmsg = string_sprintf("PGSQL: query failed: %s\n",
2ea97746 302 PQresultErrorMessage(pg_result));
420a0d19
CE
303 This was suggested by a user:
304 */
305
306 *errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
2ea97746
CE
307 PQresultErrorMessage(pg_result),
308 PQresStatus(PQresultStatus(pg_result)), query);
420a0d19 309 goto PGSQL_EXIT;
2ea97746 310 }
420a0d19
CE
311
312/* Result is in pg_result. Find the number of fields returned. If this is one,
313we don't add field names to the data. Otherwise we do. If the query did not
314return anything we skip the for loop; this also applies to the case
315PGRES_COMMAND_OK. */
316
317num_fields = PQnfields(pg_result);
318num_tuples = PQntuples(pg_result);
319
320/* Get the fields and construct the result string. If there is more than one
321row, we insert '\n' between them. */
322
323for (i = 0; i < num_tuples; i++)
324 {
2ea97746
CE
325 if (result)
326 result = string_catn(result, US"\n", 1);
420a0d19 327
2ea97746
CE
328 if (num_fields == 1)
329 result = string_catn(result,
330 US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
331 else
420a0d19
CE
332 {
333 int j;
334 for (j = 0; j < num_fields; j++)
335 {
336 uschar *tmp = US PQgetvalue(pg_result, i, j);
2ea97746 337 result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result);
420a0d19
CE
338 }
339 }
340 }
341
2ea97746 342/* If result is NULL then no data has been found and so we return FAIL. */
420a0d19 343
2ea97746 344if (!result)
420a0d19
CE
345 {
346 yield = FAIL;
347 *errmsg = US"PGSQL: no data found";
348 }
420a0d19
CE
349
350/* Get here by goto from various error checks. */
351
352PGSQL_EXIT:
353
354/* Free store for any result that was got; don't close the connection, as
355it is cached. */
356
2ea97746 357if (pg_result) PQclear(pg_result);
420a0d19 358
2ea97746 359/* Non-NULL result indicates a successful result */
420a0d19 360
2ea97746 361if (result)
420a0d19 362 {
2ea97746
CE
363 store_reset(result->s + result->ptr + 1);
364 *resultptr = string_from_gstring(result);
420a0d19
CE
365 return OK;
366 }
367else
368 {
369 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
370 return yield; /* FAIL or DEFER */
371 }
372}
373
374
375
376
377/*************************************************
378* Find entry point *
379*************************************************/
380
381/* See local README for interface description. The handle and filename
382arguments are not used. The code to loop through a list of servers while the
383query is deferred with a retryable error is now in a separate function that is
384shared with other SQL lookups. */
385
386static int
2ea97746
CE
387pgsql_find(void *handle, uschar *filename, const uschar *query, int length,
388 uschar **result, uschar **errmsg, uint *do_cache)
420a0d19
CE
389{
390return lf_sqlperform(US"PostgreSQL", US"pgsql_servers", pgsql_servers, query,
391 result, errmsg, do_cache, perform_pgsql_search);
392}
393
394
395
396/*************************************************
397* Quote entry point *
398*************************************************/
399
400/* The characters that always need to be quoted (with backslash) are newline,
401tab, carriage return, backspace, backslash itself, and the quote characters.
420a0d19
CE
402
403The original code quoted single quotes as \' which is documented as valid in
404the O'Reilly book "Practical PostgreSQL" (first edition) as an alternative to
405the SQL standard '' way of representing a single quote as data. However, in
406June 2006 there was some security issue with using \' and so this has been
407changed.
408
409[Note: There is a function called PQescapeStringConn() that quotes strings.
410This cannot be used because it needs a PGconn argument (the connection handle).
411Why, I don't know. Seems odd for just string escaping...]
412
413Arguments:
414 s the string to be quoted
415 opt additional option text or NULL if none
416
417Returns: the processed string or NULL for a bad option
418*/
419
420static uschar *
421pgsql_quote(uschar *s, uschar *opt)
422{
423register int c;
424int count = 0;
425uschar *t = s;
426uschar *quoted;
427
428if (opt != NULL) return NULL; /* No options recognized */
429
430while ((c = *t++) != 0)
2ea97746 431 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;
420a0d19
CE
432
433if (count == 0) return s;
434t = quoted = store_get(Ustrlen(s) + count + 1);
435
436while ((c = *s++) != 0)
437 {
438 if (c == '\'')
439 {
440 *t++ = '\'';
441 *t++ = '\'';
442 }
2ea97746 443 else if (Ustrchr("\n\t\r\b\"\\", c) != NULL)
420a0d19
CE
444 {
445 *t++ = '\\';
446 switch(c)
447 {
448 case '\n': *t++ = 'n';
449 break;
450 case '\t': *t++ = 't';
451 break;
452 case '\r': *t++ = 'r';
453 break;
454 case '\b': *t++ = 'b';
455 break;
456 default: *t++ = c;
457 break;
458 }
459 }
460 else *t++ = c;
461 }
462
463*t = 0;
464return quoted;
465}
466
467
468/*************************************************
469* Version reporting entry point *
470*************************************************/
471
472/* See local README for interface description. */
473
474#include "../version.h"
475
476void
477pgsql_version_report(FILE *f)
478{
479#ifdef DYNLOOKUP
480fprintf(f, "Library version: PostgreSQL: Exim version %s\n", EXIM_VERSION_STR);
481#endif
482
483/* Version reporting: there appears to be no available information about
484the client library in libpq-fe.h; once you have a connection object, you
485can access the server version and the chosen protocol version, but those
486aren't really what we want. It might make sense to debug_printf those
487when the connection is established though? */
488}
489
490
491static lookup_info _lookup_info = {
492 US"pgsql", /* lookup name */
493 lookup_querystyle, /* query-style lookup */
494 pgsql_open, /* open function */
495 NULL, /* no check function */
496 pgsql_find, /* find function */
497 NULL, /* no close function */
498 pgsql_tidy, /* tidy function */
499 pgsql_quote, /* quoting function */
500 pgsql_version_report /* version reporting */
501};
502
503#ifdef DYNLOOKUP
504#define pgsql_lookup_module_info _lookup_module_info
505#endif
506
507static lookup_info *_lookup_list[] = { &_lookup_info };
508lookup_module_info pgsql_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
509
510/* End of lookups/pgsql.c */