Another wish.
[exim.git] / src / src / lookups / mysql.c
CommitLineData
c988f1f4 1/* $Cambridge: exim/src/src/lookups/mysql.c,v 1.2 2005/01/04 10:00:44 ph10 Exp $ */
0756eb3c
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
c988f1f4 7/* Copyright (c) University of Cambridge 1995 - 2005 */
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],
205 port, CS socket, 0) == NULL)
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
294/* If result is NULL then no data has been found and so we return FAIL.
295Otherwise, we must terminate the string which has been built; string_cat()
296always leaves enough room for a terminating zero. */
297
298if (result == NULL)
299 {
300 yield = FAIL;
301 *errmsg = US"MYSQL: no data found";
302 }
303else
304 {
305 result[offset] = 0;
306 store_reset(result + offset + 1);
307 }
308
309/* Get here by goto from various error checks and from the case where no data
310was read (e.g. an update query). */
311
312MYSQL_EXIT:
313
314/* Free mysal store for any result that was got; don't close the connection, as
315it is cached. */
316
317if (mysql_result != NULL) mysql_free_result(mysql_result);
318
319/* Non-NULL result indicates a sucessful result */
320
321if (result != NULL)
322 {
323 *resultptr = result;
324 return OK;
325 }
326else
327 {
328 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
329 return yield; /* FAIL or DEFER */
330 }
331}
332
333
334
335
336/*************************************************
337* Find entry point *
338*************************************************/
339
340/* See local README for interface description. The handle and filename
341arguments are not used. Loop through a list of servers while the query is
342deferred with a retryable error. */
343
344int
345mysql_find(void *handle, uschar *filename, uschar *query, int length,
346 uschar **result, uschar **errmsg, BOOL *do_cache)
347{
348int sep = 0;
349uschar *server;
350uschar *list = mysql_servers;
351uschar buffer[512];
352
353DEBUG(D_lookup) debug_printf("MYSQL query: %s\n", query);
354
355while ((server = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
356 {
357 BOOL defer_break = FALSE;
358 int rc = perform_mysql_search(query, server, result, errmsg, &defer_break,
359 do_cache);
360 if (rc != DEFER || defer_break) return rc;
361 }
362
363if (mysql_servers == NULL)
364 *errmsg = US"no MYSQL servers defined (mysql_servers option)";
365
366return DEFER;
367}
368
369
370
371/*************************************************
372* Quote entry point *
373*************************************************/
374
375/* The only characters that need to be quoted (with backslash) are newline,
376tab, carriage return, backspace, backslash itself, and the quote characters.
377Percent, and underscore and not escaped. They are only special in contexts
378where they can be wild cards, and this isn't usually the case for data inserted
379from messages, since that isn't likely to be treated as a pattern of any kind.
380Sadly, MySQL doesn't seem to behave like other programs. If you use something
381like "where id="ab\%cd" it does not treat the string as "ab%cd". So you really
382can't quote "on spec".
383
384Arguments:
385 s the string to be quoted
386 opt additional option text or NULL if none
387
388Returns: the processed string or NULL for a bad option
389*/
390
391uschar *
392mysql_quote(uschar *s, uschar *opt)
393{
394register int c;
395int count = 0;
396uschar *t = s;
397uschar *quoted;
398
399if (opt != NULL) return NULL; /* No options recognized */
400
401while ((c = *t++) != 0)
402 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL) count++;
403
404if (count == 0) return s;
405t = quoted = store_get(Ustrlen(s) + count + 1);
406
407while ((c = *s++) != 0)
408 {
409 if (Ustrchr("\n\t\r\b\'\"\\", c) != NULL)
410 {
411 *t++ = '\\';
412 switch(c)
413 {
414 case '\n': *t++ = 'n';
415 break;
416 case '\t': *t++ = 't';
417 break;
418 case '\r': *t++ = 'r';
419 break;
420 case '\b': *t++ = 'b';
421 break;
422 default: *t++ = c;
423 break;
424 }
425 }
426 else *t++ = c;
427 }
428
429*t = 0;
430return quoted;
431}
432
433
434#endif /* MYSQL_LOOKUP */
435
436/* End of lookups/mysql.c */