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