DKIM ACL Documentation
[exim.git] / src / src / lookups / mysql.c
CommitLineData
f6efe9ce 1/* $Cambridge: exim/src/src/lookups/mysql.c,v 1.7 2010/03/05 15:59:29 nm4 Exp $ */
0756eb3c
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
0a49a7a4 7/* Copyright (c) University of Cambridge 1995 - 2009 */
0756eb3c
PH
8/* See the file NOTICE for conditions of use and distribution. */
9
10/* Thanks to Paul Kelly for contributing the original code for these
11functions. */
12
13
14#include "../exim.h"
15#include "lf_functions.h"
16#include "mysql.h" /* The local header */
17
18
19/* We can't just compile this code and allow the library mechanism to omit the
20functions if they are not wanted, because we need to have the MYSQL header
21available for compiling. Therefore, compile these functions only if
22LOOKUP_MYSQL is defined. However, some compilers don't like compiling empty
23modules, so keep them happy with a dummy when skipping the rest. Make it
24reference itself to stop picky compilers complaining that it is unused, and put
25in a dummy argument to stop even pickier compilers complaining about infinite
26loops. */
27
28#ifndef LOOKUP_MYSQL
29static void dummy(int x) { dummy(x-1); }
30#else
31
32
33#include <mysql.h> /* The system header */
34
35
36/* Structure and anchor for caching connections. */
37
38typedef struct mysql_connection {
39 struct mysql_connection *next;
40 uschar *server;
41 MYSQL *handle;
42} mysql_connection;
43
44static mysql_connection *mysql_connections = NULL;
45
46
47
48/*************************************************
49* Open entry point *
50*************************************************/
51
52/* See local README for interface description. */
53
54void *
55mysql_open(uschar *filename, uschar **errmsg)
56{
57return (void *)(1); /* Just return something non-null */
58}
59
60
61
62/*************************************************
63* Tidy entry point *
64*************************************************/
65
66/* See local README for interface description. */
67
68void
69mysql_tidy(void)
70{
71mysql_connection *cn;
72while ((cn = mysql_connections) != NULL)
73 {
74 mysql_connections = cn->next;
75 DEBUG(D_lookup) debug_printf("close MYSQL connection: %s\n", cn->server);
76 mysql_close(cn->handle);
77 }
78}
79
80
81
82/*************************************************
83* Internal search function *
84*************************************************/
85
86/* This function is called from the find entry point to do the search for a
87single server.
88
89Arguments:
90 query the query string
91 server the server string
92 resultptr where to store the result
93 errmsg where to point an error message
94 defer_break TRUE if no more servers are to be tried after DEFER
95 do_cache set false if data is changed
96
97The server string is of the form "host/dbname/user/password". The host can be
98host:port. This string is in a nextinlist temporary buffer, so can be
99overwritten.
100
101Returns: OK, FAIL, or DEFER
102*/
103
104static int
105perform_mysql_search(uschar *query, uschar *server, uschar **resultptr,
106 uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
107{
108MYSQL *mysql_handle = NULL; /* Keep compilers happy */
109MYSQL_RES *mysql_result = NULL;
110MYSQL_ROW mysql_row_data;
111MYSQL_FIELD *fields;
112
113int i;
114int ssize = 0;
115int offset = 0;
116int yield = DEFER;
117unsigned int num_fields;
118uschar *result = NULL;
119mysql_connection *cn;
120uschar *server_copy = NULL;
121uschar *sdata[4];
122
123/* Disaggregate the parameters from the server argument. The order is host,
124database, user, password. We can write to the string, since it is in a
125nextinlist temporary buffer. The copy of the string that is used for caching
126has the password removed. This copy is also used for debugging output. */
127
128for (i = 3; i > 0; i--)
129 {
130 uschar *pp = Ustrrchr(server, '/');
131 if (pp == NULL)
132 {
133 *errmsg = string_sprintf("incomplete MySQL server data: %s",
134 (i == 3)? server : server_copy);
135 *defer_break = TRUE;
136 return DEFER;
137 }
138 *pp++ = 0;
139 sdata[i] = pp;
140 if (i == 3) server_copy = string_copy(server); /* sans password */
141 }
142sdata[0] = server; /* What's left at the start */
143
144/* See if we have a cached connection to the server */
145
146for (cn = mysql_connections; cn != NULL; cn = cn->next)
147 {
148 if (Ustrcmp(cn->server, server_copy) == 0)
149 {
150 mysql_handle = cn->handle;
151 break;
152 }
153 }
154
155/* If no cached connection, we must set one up. Mysql allows for a host name
156and port to be specified. It also allows the name of a Unix socket to be used.
157Unfortunately, this contains slashes, but its use is expected to be rare, so
158the rather cumbersome syntax shouldn't inconvenience too many people. We use
159this: host:port(socket) where all the parts are optional. */
160
161if (cn == NULL)
162 {
163 uschar *p;
164 uschar *socket = NULL;
165 int port = 0;
166
167 if ((p = Ustrchr(sdata[0], '(')) != NULL)
168 {
169 *p++ = 0;
170 socket = p;
171 while (*p != 0 && *p != ')') p++;
172 *p = 0;
173 }
174
175 if ((p = Ustrchr(sdata[0], ':')) != NULL)
176 {
177 *p++ = 0;
178 port = Uatoi(p);
179 }
180
181 if (Ustrchr(sdata[0], '/') != NULL)
182 {
183 *errmsg = string_sprintf("unexpected slash in MySQL server hostname: %s",
184 sdata[0]);
185 *defer_break = TRUE;
186 return DEFER;
187 }
188
189 /* If the database is the empty string, set it NULL - the query must then
190 define it. */
191
192 if (sdata[1][0] == 0) sdata[1] = NULL;
193
194 DEBUG(D_lookup)
195 debug_printf("MYSQL new connection: host=%s port=%d socket=%s "
196 "database=%s user=%s\n", sdata[0], port, socket, sdata[1], sdata[2]);
197
198 /* Get store for a new handle, initialize it, and connect to the server */
199
200 mysql_handle = store_get(sizeof(MYSQL));
201 mysql_init(mysql_handle);
202 if (mysql_real_connect(mysql_handle,
203 /* host user passwd database */
204 CS sdata[0], CS sdata[2], CS sdata[3], CS sdata[1],
f6efe9ce 205 port, CS socket, CLIENT_MULTI_RESULTS) == NULL)
0756eb3c
PH
206 {
207 *errmsg = string_sprintf("MYSQL connection failed: %s",
208 mysql_error(mysql_handle));
209 *defer_break = FALSE;
210 goto MYSQL_EXIT;
211 }
212
213 /* Add the connection to the cache */
214
215 cn = store_get(sizeof(mysql_connection));
216 cn->server = server_copy;
217 cn->handle = mysql_handle;
218 cn->next = mysql_connections;
219 mysql_connections = cn;
220 }
221
222/* Else use a previously cached connection */
223
224else
225 {
226 DEBUG(D_lookup)
227 debug_printf("MYSQL using cached connection for %s\n", server_copy);
228 }
229
230/* Run the query */
231
232if (mysql_query(mysql_handle, CS query) != 0)
233 {
234 *errmsg = string_sprintf("MYSQL: query failed: %s\n",
235 mysql_error(mysql_handle));
236 *defer_break = FALSE;
237 goto MYSQL_EXIT;
238 }
239
240/* Pick up the result. If the query was not of the type that returns data,
241namely INSERT, UPDATE, or DELETE, an error occurs here. However, this situation
242can be detected by calling mysql_field_count(). If its result is zero, no data
243was expected (this is all explained clearly in the MySQL manual). In this case,
244we return the number of rows affected by the command. In this event, we do NOT
245want to cache the result; also the whole cache for the handle must be cleaned
246up. Setting do_cache FALSE requests this. */
247
248if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
249 {
250 if ( mysql_field_count(mysql_handle) == 0 )
251 {
252 DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
253 result = string_sprintf("%d", mysql_affected_rows(mysql_handle));
254 *do_cache = FALSE;
255 goto MYSQL_EXIT;
256 }
257 *errmsg = string_sprintf("MYSQL: lookup result failed: %s\n",
258 mysql_error(mysql_handle));
259 *defer_break = FALSE;
260 goto MYSQL_EXIT;
261 }
262
263/* Find the number of fields returned. If this is one, we don't add field
264names to the data. Otherwise we do. */
265
266num_fields = mysql_num_fields(mysql_result);
267
268/* Get the fields and construct the result string. If there is more than one
269row, we insert '\n' between them. */
270
271fields = mysql_fetch_fields(mysql_result);
272
273while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
274 {
275 unsigned long *lengths = mysql_fetch_lengths(mysql_result);
276
277 if (result != NULL)
278 result = string_cat(result, &ssize, &offset, US"\n", 1);
279
280 if (num_fields == 1)
281 {
282 if (mysql_row_data[0] != NULL) /* NULL value yields nothing */
283 result = string_cat(result, &ssize, &offset, US mysql_row_data[0],
284 lengths[0]);
285 }
286
287 else for (i = 0; i < num_fields; i++)
288 {
289 result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
290 result, &ssize, &offset);
291 }
292 }
293
f6efe9ce
NM
294/* more results? -1 = no, >0 = error, 0 = yes (keep looping)
295 This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
296 we don't expect any more results. */
297
298while((i = mysql_next_result(mysql_handle)) >= 0) {
299 if(i == 0) { /* Just ignore more results */
300 DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
301 continue;
302 }
303
304 *errmsg = string_sprintf("MYSQL: lookup result error when checking for more results: %s\n",
305 mysql_error(mysql_handle));
306 goto MYSQL_EXIT;
307}
308
0756eb3c
PH
309/* If result is NULL then no data has been found and so we return FAIL.
310Otherwise, we must terminate the string which has been built; string_cat()
311always leaves enough room for a terminating zero. */
312
313if (result == NULL)
314 {
315 yield = FAIL;
316 *errmsg = US"MYSQL: no data found";
317 }
318else
319 {
320 result[offset] = 0;
321 store_reset(result + offset + 1);
322 }
323
324/* Get here by goto from various error checks and from the case where no data
325was read (e.g. an update query). */
326
327MYSQL_EXIT:
328
329/* Free mysal store for any result that was got; don't close the connection, as
330it is cached. */
331
332if (mysql_result != NULL) mysql_free_result(mysql_result);
333
334/* Non-NULL result indicates a sucessful result */
335
336if (result != NULL)
337 {
338 *resultptr = result;
339 return OK;
340 }
341else
342 {
343 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
344 return yield; /* FAIL or DEFER */
345 }
346}
347
348
349
350
351/*************************************************
352* Find entry point *
353*************************************************/
354
355/* See local README for interface description. The handle and filename
b7670459
PH
356arguments are not used. The code to loop through a list of servers while the
357query is deferred with a retryable error is now in a separate function that is
358shared with other SQL lookups. */
0756eb3c
PH
359
360int
361mysql_find(void *handle, uschar *filename, uschar *query, int length,
362 uschar **result, uschar **errmsg, BOOL *do_cache)
363{
b7670459
PH
364return lf_sqlperform(US"MySQL", US"mysql_servers", mysql_servers, query,
365 result, errmsg, do_cache, perform_mysql_search);
0756eb3c
PH
366}
367
368
369
370/*************************************************
371* Quote entry point *
372*************************************************/
373
374/* The only characters that need to be quoted (with backslash) are newline,
375tab, carriage return, backspace, backslash itself, and the quote characters.
376Percent, and underscore and not escaped. They are only special in contexts
377where they can be wild cards, and this isn't usually the case for data inserted
378from messages, since that isn't likely to be treated as a pattern of any kind.
379Sadly, MySQL doesn't seem to behave like other programs. If you use something
380like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
381can't quote "on spec".
382
383Arguments:
384 s the string to be quoted
385 opt additional option text or NULL if none
386
387Returns: the processed string or NULL for a bad option
388*/
389
390uschar *
391mysql_quote(uschar *s, uschar *opt)
392{
393register int c;
394int count = 0;
395uschar *t = s;
396uschar *quoted;
397
398if (opt != NULL) return NULL; /* No options recognized */
399
400while ((c = *t++) != 0)
401 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;
402
403if (count == 0) return s;
404t = quoted = store_get(Ustrlen(s) + count + 1);
405
406while ((c = *s++) != 0)
407 {
408 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
409 {
410 *t++ = '\\';
411 switch(c)
412 {
413 case '\n': *t++ = 'n';
414 break;
415 case '\t': *t++ = 't';
416 break;
417 case '\r': *t++ = 'r';
418 break;
419 case '\b': *t++ = 'b';
420 break;
421 default: *t++ = c;
422 break;
423 }
424 }
425 else *t++ = c;
426 }
427
428*t = 0;
429return quoted;
430}
431
432
433#endif /* MYSQL_LOOKUP */
434
435/* End of lookups/mysql.c */