4b947e2054b52fd7411a93a66ee2ff7635d79bc4
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_mysql / rlm_sql_mysql.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_sql_mysql.c
20  * @brief MySQL driver.
21  *
22  * @copyright 2014-2015  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  * @copyright 2000-2007,2015  The FreeRADIUS server project
24  * @copyright 2000  Mike Machado <mike@innercite.com>
25  * @copyright 2000  Alan DeKok <aland@ox.org>
26  */
27 RCSID("$Id$")
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/rad_assert.h>
31
32 #include <sys/stat.h>
33
34 #include "config.h"
35
36 #ifdef HAVE_MYSQL_MYSQL_H
37 #  include <mysql/mysql_version.h>
38 #  include <mysql/errmsg.h>
39 #  include <mysql/mysql.h>
40 #  include <mysql/mysqld_error.h>
41 #elif defined(HAVE_MYSQL_H)
42 #  include <mysql_version.h>
43 #  include <errmsg.h>
44 #  include <mysql.h>
45 #  include <mysqld_error.h>
46 #endif
47
48 #include "rlm_sql.h"
49
50 static int mysql_instance_count = 0;
51
52 typedef enum {
53         SERVER_WARNINGS_AUTO = 0,
54         SERVER_WARNINGS_YES,
55         SERVER_WARNINGS_NO
56 } rlm_sql_mysql_warnings;
57
58 static const FR_NAME_NUMBER server_warnings_table[] = {
59         { "auto",       SERVER_WARNINGS_AUTO    },
60         { "yes",        SERVER_WARNINGS_YES     },
61         { "no",         SERVER_WARNINGS_NO      },
62         { NULL, 0 }
63 };
64
65 typedef struct rlm_sql_mysql_conn {
66         MYSQL           db;
67         MYSQL           *sock;
68         MYSQL_RES       *result;
69         rlm_sql_row_t   row;
70 } rlm_sql_mysql_conn_t;
71
72 typedef struct rlm_sql_mysql_config {
73         char const *tls_ca_file;                //!< Path to the CA used to validate the server's certificate.
74         char const *tls_ca_path;                //!< Directory containing CAs that may be used to validate the
75                                                 //!< servers certificate.
76         char const *tls_certificate_file;       //!< Public certificate we present to the server.
77         char const *tls_private_key_file;       //!< Private key for the certificate we present to the server.
78         char const *tls_cipher;
79
80         char const *warnings_str;               //!< Whether we always query the server for additional warnings.
81         rlm_sql_mysql_warnings  warnings;       //!< mysql_warning_count() doesn't
82                                                 //!< appear to work with NDB cluster
83 } rlm_sql_mysql_config_t;
84
85 static CONF_PARSER tls_config[] = {
86         { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_file), NULL },
87         { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_ca_path), NULL },
88         { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_certificate_file), NULL },
89         { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_sql_mysql_config_t, tls_private_key_file), NULL },
90
91         /*
92          *      MySQL Specific TLS attributes
93          */
94         { "cipher", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, tls_cipher), NULL },
95
96         { NULL, -1, 0, NULL, NULL }
97 };
98
99 static const CONF_PARSER driver_config[] = {
100         { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
101
102         { "warnings", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_mysql_config_t, warnings_str), "auto" },
103
104         {NULL, -1, 0, NULL, NULL}
105 };
106
107 /* Prototypes */
108 static sql_rcode_t sql_free_result(rlm_sql_handle_t*, rlm_sql_config_t*);
109
110 static int _sql_socket_destructor(rlm_sql_mysql_conn_t *conn)
111 {
112         DEBUG2("rlm_sql_mysql: Socket destructor called, closing socket");
113
114         if (conn->sock){
115                 mysql_close(conn->sock);
116         }
117
118         return 0;
119 }
120
121 static int _mod_destructor(UNUSED rlm_sql_mysql_config_t *driver)
122 {
123         if (--mysql_instance_count == 0) mysql_library_end();
124
125         return 0;
126 }
127
128 static int mod_instantiate(CONF_SECTION *conf, rlm_sql_config_t *config)
129 {
130         rlm_sql_mysql_config_t *driver;
131         int warnings;
132
133         static bool version_done = false;
134
135         if (!version_done) {
136                 version_done = true;
137
138                 INFO("rlm_sql_mysql: libmysql version: %s", mysql_get_client_info());
139         }
140
141         if (mysql_instance_count == 0) {
142                 if (mysql_library_init(0, NULL, NULL)) {
143                         ERROR("rlm_sql_mysql: libmysql initialisation failed");
144
145                         return -1;
146                 }
147         }
148         mysql_instance_count++;
149
150         MEM(driver = config->driver = talloc_zero(config, rlm_sql_mysql_config_t));
151         talloc_set_destructor(driver, _mod_destructor);
152
153         if (cf_section_parse(conf, driver, driver_config) < 0) {
154                 return -1;
155         }
156
157         warnings = fr_str2int(server_warnings_table, driver->warnings_str, -1);
158         if (warnings < 0) {
159                 ERROR("rlm_sql_mysql: Invalid warnings value \"%s\", must be yes, no, or auto", driver->warnings_str);
160                 return -1;
161         }
162         driver->warnings = (rlm_sql_mysql_warnings)warnings;
163
164         return 0;
165 }
166
167 static sql_rcode_t sql_socket_init(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
168 {
169         rlm_sql_mysql_conn_t *conn;
170         rlm_sql_mysql_config_t *driver = config->driver;
171         unsigned long sql_flags;
172
173         MEM(conn = handle->conn = talloc_zero(handle, rlm_sql_mysql_conn_t));
174         talloc_set_destructor(conn, _sql_socket_destructor);
175
176         DEBUG("rlm_sql_mysql: Starting connect to MySQL server");
177
178         mysql_init(&(conn->db));
179
180         /*
181          *      If any of the TLS options are set, configure TLS
182          *
183          *      According to MySQL docs this function always returns 0, so we won't
184          *      know if ssl setup succeeded until mysql_real_connect is called below.
185          */
186         if (driver->tls_ca_file || driver->tls_ca_path ||
187             driver->tls_certificate_file || driver->tls_private_key_file) {
188                 mysql_ssl_set(&(conn->db), driver->tls_private_key_file, driver->tls_certificate_file,
189                               driver->tls_ca_file, driver->tls_ca_path, driver->tls_cipher);
190         }
191
192         mysql_options(&(conn->db), MYSQL_READ_DEFAULT_GROUP, "freeradius");
193
194         /*
195          *      We need to know about connection errors, and are capable
196          *      of reconnecting automatically.
197          */
198 #ifdef MYSQL_OPT_RECONNECT
199         {
200                 my_bool reconnect = 0;
201                 mysql_options(&(conn->db), MYSQL_OPT_RECONNECT, &reconnect);
202         }
203 #endif
204
205 #if (MYSQL_VERSION_ID >= 50000)
206         if (config->query_timeout) {
207                 unsigned int connect_timeout = config->query_timeout;
208                 unsigned int read_timeout = config->query_timeout;
209                 unsigned int write_timeout = config->query_timeout;
210
211                 /*
212                  *      The timeout in seconds for each attempt to read from the server.
213                  *      There are retries if necessary, so the total effective timeout
214                  *      value is three times the option value.
215                  */
216                 if (config->query_timeout >= 3) read_timeout /= 3;
217
218                 /*
219                  *      The timeout in seconds for each attempt to write to the server.
220                  *      There is a retry if necessary, so the total effective timeout
221                  *      value is two times the option value.
222                  */
223                 if (config->query_timeout >= 2) write_timeout /= 2;
224
225                 /*
226                  *      Connect timeout is actually connect timeout (according to the
227                  *      docs) there are no automatic retries.
228                  */
229                 mysql_options(&(conn->db), MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
230                 mysql_options(&(conn->db), MYSQL_OPT_READ_TIMEOUT, &read_timeout);
231                 mysql_options(&(conn->db), MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
232         }
233 #endif
234
235 #if (MYSQL_VERSION_ID >= 40100)
236         sql_flags = CLIENT_MULTI_RESULTS | CLIENT_FOUND_ROWS;
237 #else
238         sql_flags = CLIENT_FOUND_ROWS;
239 #endif
240
241 #ifdef CLIENT_MULTI_STATEMENTS
242         sql_flags |= CLIENT_MULTI_STATEMENTS;
243 #endif
244         conn->sock = mysql_real_connect(&(conn->db),
245                                         config->sql_server,
246                                         config->sql_login,
247                                         config->sql_password,
248                                         config->sql_db,
249                                         config->sql_port,
250                                         NULL,
251                                         sql_flags);
252         if (!conn->sock) {
253                 ERROR("rlm_sql_mysql: Couldn't connect to MySQL server %s@%s:%s", config->sql_login,
254                       config->sql_server, config->sql_db);
255                 ERROR("rlm_sql_mysql: MySQL error: %s", mysql_error(&conn->db));
256
257                 conn->sock = NULL;
258                 return RLM_SQL_ERROR;
259         }
260
261         DEBUG2("rlm_sql_mysql: Connected to database '%s' on %s, server version %s, protocol version %i",
262                config->sql_db, mysql_get_host_info(conn->sock),
263                mysql_get_server_info(conn->sock), mysql_get_proto_info(conn->sock));
264
265         return RLM_SQL_OK;
266 }
267
268 /** Analyse the last error that occurred on the socket, and determine an action
269  *
270  * @param server Socket from which to extract the server error. May be NULL.
271  * @param client_errno Error from the client.
272  * @return an action for rlm_sql to take.
273  */
274 static sql_rcode_t sql_check_error(MYSQL *server, int client_errno)
275 {
276         int sql_errno = 0;
277
278         /*
279          *      The client and server error numbers are in the
280          *      same numberspace.
281          */
282         if (server) sql_errno = mysql_errno(server);
283         if ((sql_errno == 0) && (client_errno != 0)) sql_errno = client_errno;
284
285         if (sql_errno > 0) switch (sql_errno) {
286         case CR_SERVER_GONE_ERROR:
287         case CR_SERVER_LOST:
288         case -1:
289                 return RLM_SQL_RECONNECT;
290
291         case CR_OUT_OF_MEMORY:
292         case CR_COMMANDS_OUT_OF_SYNC:
293         case CR_UNKNOWN_ERROR:
294         default:
295                 return RLM_SQL_ERROR;
296
297         /*
298          *      Constraints errors that signify a duplicate, or that we might
299          *      want to try an alternative query.
300          *
301          *      Error constants not found in the 3.23/4.0/4.1 manual page
302          *      are checked for.
303          *      Other error constants should always be available.
304          */
305         case ER_DUP_UNIQUE:                     /* Can't write, because of unique constraint, to table '%s'. */
306         case ER_DUP_KEY:                        /* Can't write; duplicate key in table '%s' */
307
308         case ER_DUP_ENTRY:                      /* Duplicate entry '%s' for key %d. */
309         case ER_NO_REFERENCED_ROW:              /* Cannot add or update a child row: a foreign key constraint fails */
310         case ER_ROW_IS_REFERENCED:              /* Cannot delete or update a parent row: a foreign key constraint fails */
311 #ifdef ER_FOREIGN_DUPLICATE_KEY
312         case ER_FOREIGN_DUPLICATE_KEY:          /* Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry. */
313 #endif
314 #ifdef ER_DUP_ENTRY_WITH_KEY_NAME
315         case ER_DUP_ENTRY_WITH_KEY_NAME:        /* Duplicate entry '%s' for key '%s' */
316 #endif
317 #ifdef ER_NO_REFERENCED_ROW_2
318         case ER_NO_REFERENCED_ROW_2:
319 #endif
320 #ifdef ER_ROW_IS_REFERENCED_2
321         case ER_ROW_IS_REFERENCED_2:
322 #endif
323                 return RLM_SQL_ALT_QUERY;
324
325         /*
326          *      Constraints errors that signify an invalid query
327          *      that can never succeed.
328          */
329         case ER_BAD_NULL_ERROR:                 /* Column '%s' cannot be null */
330         case ER_NON_UNIQ_ERROR:                 /* Column '%s' in %s is ambiguous */
331                 return RLM_SQL_QUERY_INVALID;
332
333         }
334
335         return RLM_SQL_OK;
336 }
337
338 static sql_rcode_t sql_query(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config, char const *query)
339 {
340         rlm_sql_mysql_conn_t *conn = handle->conn;
341         sql_rcode_t rcode;
342         char const *info;
343
344         if (!conn->sock) {
345                 ERROR("rlm_sql_mysql: Socket not connected");
346                 return RLM_SQL_RECONNECT;
347         }
348
349         mysql_query(conn->sock, query);
350         rcode = sql_check_error(conn->sock, 0);
351         if (rcode != RLM_SQL_OK) {
352                 return rcode;
353         }
354
355         /* Only returns non-null string for INSERTS */
356         info = mysql_info(conn->sock);
357         if (info) DEBUG2("rlm_sql_mysql: %s", info);
358
359         return RLM_SQL_OK;
360 }
361
362 static sql_rcode_t sql_store_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
363 {
364         rlm_sql_mysql_conn_t *conn = handle->conn;
365         sql_rcode_t rcode;
366         int ret;
367
368         if (!conn->sock) {
369                 ERROR("rlm_sql_mysql: Socket not connected");
370                 return RLM_SQL_RECONNECT;
371         }
372
373 retry_store_result:
374         if (!(conn->result = mysql_store_result(conn->sock))) {
375                 rcode = sql_check_error(conn->sock, 0);
376                 if (rcode != RLM_SQL_OK) return rcode;
377 #if (MYSQL_VERSION_ID >= 40100)
378                 ret = mysql_next_result(conn->sock);
379                 if (ret == 0) {
380                         /* there are more results */
381                         goto retry_store_result;
382                 } else if (ret > 0) return sql_check_error(NULL, ret);
383 #endif
384         }
385         return RLM_SQL_OK;
386 }
387
388 static int sql_num_fields(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
389 {
390         int num = 0;
391         rlm_sql_mysql_conn_t *conn = handle->conn;
392
393 #if MYSQL_VERSION_ID >= 32224
394         if (!(num = mysql_field_count(conn->sock))) {
395 #else
396         if (!(num = mysql_num_fields(conn->sock))) {
397 #endif
398                 return -1;
399         }
400         return num;
401 }
402
403 static sql_rcode_t sql_select_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config, char const *query)
404 {
405         sql_rcode_t rcode;
406
407         rcode = sql_query(handle, config, query);
408         if (rcode != RLM_SQL_OK) {
409                 return rcode;
410         }
411
412         rcode = sql_store_result(handle, config);
413         if (rcode != RLM_SQL_OK) {
414                 return rcode;
415         }
416
417         /* Why? Per http://www.mysql.com/doc/n/o/node_591.html,
418          * this cannot return an error.  Perhaps just to complain if no
419          * fields are found?
420          */
421         sql_num_fields(handle, config);
422
423         return rcode;
424 }
425
426 static int sql_num_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
427 {
428         rlm_sql_mysql_conn_t *conn = handle->conn;
429
430         if (conn->result) {
431                 return mysql_num_rows(conn->result);
432         }
433
434         return 0;
435 }
436
437 static sql_rcode_t sql_fields(char const **out[], rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
438 {
439         rlm_sql_mysql_conn_t *conn = handle->conn;
440
441         unsigned int    fields, i;
442         MYSQL_FIELD     *field_info;
443         char const      **names;
444
445         fields = mysql_num_fields(conn->result);
446         if (fields == 0) return RLM_SQL_ERROR;
447
448         /*
449          *      https://bugs.mysql.com/bug.php?id=32318
450          *      Hints that we don't have to free field_info.
451          */
452         field_info = mysql_fetch_fields(conn->result);
453         if (!field_info) return RLM_SQL_ERROR;
454
455         MEM(names = talloc_zero_array(handle, char const *, fields + 1));
456
457         for (i = 0; i < fields; i++) names[i] = field_info[i].name;
458         *out = names;
459
460         return RLM_SQL_OK;
461 }
462
463 static sql_rcode_t sql_fetch_row(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
464 {
465         rlm_sql_mysql_conn_t *conn = handle->conn;
466         sql_rcode_t rcode;
467         int ret;
468
469         /*
470          *  Check pointer before de-referencing it.
471          */
472         if (!conn->result) {
473                 return RLM_SQL_RECONNECT;
474         }
475
476 retry_fetch_row:
477         handle->row = mysql_fetch_row(conn->result);
478         if (!handle->row) {
479                 rcode = sql_check_error(conn->sock, 0);
480                 if (rcode != RLM_SQL_OK) return rcode;
481
482 #if (MYSQL_VERSION_ID >= 40100)
483                 sql_free_result(handle, config);
484
485                 ret = mysql_next_result(conn->sock);
486                 if (ret == 0) {
487                         /* there are more results */
488                         if ((sql_store_result(handle, config) == 0) && (conn->result != NULL)) {
489                                 goto retry_fetch_row;
490                         }
491                 } else if (ret > 0) return sql_check_error(NULL, ret);
492 #endif
493         }
494         return RLM_SQL_OK;
495 }
496
497 static sql_rcode_t sql_free_result(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
498 {
499         rlm_sql_mysql_conn_t *conn = handle->conn;
500
501         if (conn->result) {
502                 mysql_free_result(conn->result);
503                 conn->result = NULL;
504         }
505
506         return RLM_SQL_OK;
507 }
508
509 /** Retrieves any warnings associated with the last query
510  *
511  * MySQL stores a limited number of warnings associated with the last query
512  * executed. These can be very useful in diagnosing issues, or in some cases
513  * working around bugs in MySQL which causes it to return the wrong error.
514  *
515  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
516  *
517  * @param ctx to allocate temporary error buffers in.
518  * @param out Array of sql_log_entrys to fill.
519  * @param outlen Length of out array.
520  * @param handle rlm_sql connection handle.
521  * @param config rlm_sql config.
522  * @return number of errors written to the sql_log_entry array or -1 on error.
523  */
524 static size_t sql_warnings(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
525                            rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
526 {
527         rlm_sql_mysql_conn_t    *conn = handle->conn;
528
529         MYSQL_RES               *result;
530         MYSQL_ROW               row;
531         unsigned int            num_fields;
532         size_t                  i = 0;
533
534         if (outlen == 0) return 0;
535
536         /*
537          *      Retrieve any warnings associated with the previous query
538          *      that were left lingering on the server.
539          */
540         if (mysql_query(conn->sock, "SHOW WARNINGS") != 0) return -1;
541         result = mysql_store_result(conn->sock);
542         if (!result) return -1;
543
544         /*
545          *      Fields should be [0] = Level, [1] = Code, [2] = Message
546          */
547         num_fields = mysql_field_count(conn->sock);
548         if (num_fields < 3) {
549                 WARN("rlm_sql_mysql: Failed retrieving warnings, expected 3 fields got %u", num_fields);
550                 mysql_free_result(result);
551
552                 return -1;
553         }
554
555         while ((row = mysql_fetch_row(result))) {
556                 char *msg = NULL;
557                 log_type_t type;
558
559                 /*
560                  *      Translate the MySQL log level into our internal
561                  *      log levels, so they get colourised correctly.
562                  */
563                 if (strcasecmp(row[0], "warning") == 0) type = L_WARN;
564                 else if (strcasecmp(row[0], "note") == 0) type = L_DBG;
565                 else type = L_ERR;
566
567                 msg = talloc_asprintf(ctx, "%s: %s", row[1], row[2]);
568                 out[i].type = type;
569                 out[i].msg = msg;
570                 if (++i == outlen) break;
571         }
572
573         mysql_free_result(result);
574
575         return i;
576 }
577
578 /** Retrieves any errors associated with the connection handle
579  *
580  * @note Caller should free any memory allocated in ctx (talloc_free_children()).
581  *
582  * @param ctx to allocate temporary error buffers in.
583  * @param out Array of sql_log_entrys to fill.
584  * @param outlen Length of out array.
585  * @param handle rlm_sql connection handle.
586  * @param config rlm_sql config.
587  * @return number of errors written to the sql_log_entry array.
588  */
589 static size_t sql_error(TALLOC_CTX *ctx, sql_log_entry_t out[], size_t outlen,
590                         rlm_sql_handle_t *handle, rlm_sql_config_t *config)
591 {
592         rlm_sql_mysql_conn_t    *conn = handle->conn;
593         rlm_sql_mysql_config_t  *driver = config->driver;
594         char const              *error;
595         size_t                  i = 0;
596
597         rad_assert(conn && conn->sock);
598         rad_assert(outlen > 0);
599
600         error = mysql_error(conn->sock);
601
602         /*
603          *      Grab the error now in case it gets cleared on the next operation.
604          */
605         if (error && (error[0] != '\0')) {
606                 error = talloc_asprintf(ctx, "ERROR %u (%s): %s", mysql_errno(conn->sock), error,
607                                         mysql_sqlstate(conn->sock));
608         }
609
610         /*
611          *      Don't attempt to get errors from the server, if the last error
612          *      was that the server was unavailable.
613          */
614         if ((outlen > 1) && (sql_check_error(conn->sock, 0) != RLM_SQL_RECONNECT)) {
615                 size_t ret;
616                 unsigned int msgs;
617
618                 switch (driver->warnings) {
619                 case SERVER_WARNINGS_AUTO:
620                         /*
621                          *      Check to see if any warnings can be retrieved from the server.
622                          */
623                         msgs = mysql_warning_count(conn->sock);
624                         if (msgs == 0) {
625                                 DEBUG3("rlm_sql_mysql: No additional diagnostic info on server");
626                                 break;
627                         }
628
629                 /* FALL-THROUGH */
630                 case SERVER_WARNINGS_YES:
631                         ret = sql_warnings(ctx, out, outlen - 1, handle, config);
632                         if (ret > 0) i += ret;
633                         break;
634
635                 case SERVER_WARNINGS_NO:
636                         break;
637
638                 default:
639                         rad_assert(0);
640                 }
641         }
642
643         if (error) {
644                 out[i].type = L_ERR;
645                 out[i].msg = error;
646         }
647         i++;
648
649         return i;
650 }
651
652 /** Finish query
653  *
654  * As a single SQL statement may return multiple results
655  * sets, (for example stored procedures) it is necessary to check
656  * whether more results exist and process them in turn if so.
657  *
658  */
659 static sql_rcode_t sql_finish_query(rlm_sql_handle_t *handle, rlm_sql_config_t *config)
660 {
661 #if (MYSQL_VERSION_ID >= 40100)
662         rlm_sql_mysql_conn_t    *conn = handle->conn;
663         int                     ret;
664         MYSQL_RES               *result;
665
666         /*
667          *      If there's no result associated with the
668          *      connection handle, assume the first result in the
669          *      result set hasn't been retrieved.
670          *
671          *      MySQL docs says there's no performance penalty for
672          *      calling mysql_store_result for queries which don't
673          *      return results.
674          */
675         if (conn->result == NULL) {
676                 result = mysql_store_result(conn->sock);
677                 if (result) mysql_free_result(result);
678         /*
679          *      ...otherwise call sql_free_result to free an
680          *      already stored result.
681          */
682         } else {
683                 sql_free_result(handle, config);        /* sql_free_result sets conn->result to NULL */
684         }
685
686         /*
687          *      Drain any other results associated with the handle
688          *
689          *      mysql_next_result advances the result cursor so that
690          *      the next call to mysql_store_result will retrieve
691          *      the next result from the server.
692          *
693          *      Unfortunately this really does appear to be the
694          *      only way to return the handle to a consistent state.
695          */
696         while (((ret = mysql_next_result(conn->sock)) == 0) &&
697                (result = mysql_store_result(conn->sock))) {
698                 mysql_free_result(result);
699         }
700         if (ret > 0) return sql_check_error(NULL, ret);
701 #endif
702         return RLM_SQL_OK;
703 }
704
705 static int sql_affected_rows(rlm_sql_handle_t *handle, UNUSED rlm_sql_config_t *config)
706 {
707         rlm_sql_mysql_conn_t *conn = handle->conn;
708
709         return mysql_affected_rows(conn->sock);
710 }
711
712
713 /* Exported to rlm_sql */
714 extern rlm_sql_module_t rlm_sql_mysql;
715 rlm_sql_module_t rlm_sql_mysql = {
716         .name                           = "rlm_sql_mysql",
717         .flags                          = RLM_SQL_RCODE_FLAGS_ALT_QUERY,
718         .mod_instantiate                = mod_instantiate,
719         .sql_socket_init                = sql_socket_init,
720         .sql_query                      = sql_query,
721         .sql_select_query               = sql_select_query,
722         .sql_store_result               = sql_store_result,
723         .sql_num_fields                 = sql_num_fields,
724         .sql_num_rows                   = sql_num_rows,
725         .sql_affected_rows              = sql_affected_rows,
726         .sql_fields                     = sql_fields,
727         .sql_fetch_row                  = sql_fetch_row,
728         .sql_free_result                = sql_free_result,
729         .sql_error                      = sql_error,
730         .sql_finish_query               = sql_finish_query,
731         .sql_finish_select_query        = sql_finish_query
732 };