Feature: allow the sanity SQL to be customized per connection class (Issue 6453)
[asterisk/asterisk.git] / res / res_odbc.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * res_odbc.c <ODBC resource manager>
9  * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com>
10  *
11  * See http://www.asterisk.org for more information about
12  * the Asterisk project. Please do not directly contact
13  * any of the maintainers of this project for assistance;
14  * the project provides a web site, mailing lists and IRC
15  * channels for your use.
16  *
17  * This program is free software, distributed under the terms of
18  * the GNU General Public License Version 2. See the LICENSE file
19  * at the top of the source tree.
20  */
21
22 /*! \file
23  *
24  * \brief ODBC resource manager
25  * 
26  * \author Mark Spencer <markster@digium.com>
27  * \author Anthony Minessale II <anthmct@yahoo.com>
28  *
29  * \arg See also: \ref cdr_odbc
30  */
31
32 /*** MODULEINFO
33         <depend>unixodbc</depend>
34  ***/
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <string.h>
44
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/config.h"
49 #include "asterisk/options.h"
50 #include "asterisk/pbx.h"
51 #include "asterisk/module.h"
52 #include "asterisk/cli.h"
53 #include "asterisk/lock.h"
54 #include "asterisk/res_odbc.h"
55
56 struct odbc_class
57 {
58         AST_LIST_ENTRY(odbc_class) list;
59         char name[80];
60         char dsn[80];
61         char username[80];
62         char password[80];
63         char sanitysql[256];
64         SQLHENV env;
65         unsigned int haspool:1;         /* Boolean - TDS databases need this */
66         unsigned int limit:10;          /* Gives a limit of 1023 maximum */
67         unsigned int count:10;          /* Running count of pooled connections */
68         unsigned int delme:1;                   /* Purge the class */
69         AST_LIST_HEAD(, odbc_obj) odbc_obj;
70 };
71
72 AST_LIST_HEAD_STATIC(odbc_list, odbc_class);
73
74 static odbc_status odbc_obj_connect(struct odbc_obj *obj);
75 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
76 static int odbc_register_class(struct odbc_class *class, int connect);
77
78
79 SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
80 {
81         int res = 0, i, attempt;
82         SQLINTEGER nativeerror=0, numfields=0;
83         SQLSMALLINT diagbytes=0;
84         unsigned char state[10], diagnostic[256];
85         SQLHSTMT stmt;
86
87         for (attempt = 0; attempt < 2; attempt++) {
88                 /* This prepare callback may do more than just prepare -- it may also
89                  * bind parameters, bind results, etc.  The real key, here, is that
90                  * when we disconnect, all handles become invalid for most databases.
91                  * We must therefore redo everything when we establish a new
92                  * connection. */
93                 stmt = prepare_cb(obj, data);
94
95                 if (stmt) {
96                         res = SQLExecute(stmt);
97                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
98                                 if (res == SQL_ERROR) {
99                                         SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
100                                         for (i=0; i< numfields + 1; i++) {
101                                                 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
102                                                 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
103                                                 if (i > 10) {
104                                                         ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
105                                                         break;
106                                                 }
107                                         }
108                                 }
109
110                                 ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
111                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
112                                 stmt = NULL;
113
114                                 obj->up = 0;
115                                 /*
116                                  * While this isn't the best way to try to correct an error, this won't automatically
117                                  * fail when the statement handle invalidates.
118                                  */
119                                 ast_odbc_sanity_check(obj);
120                                 continue;
121                         }
122                         break;
123                 } else if (attempt == 0)
124                         ast_odbc_sanity_check(obj);
125         }
126
127         return stmt;
128 }
129
130 int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) 
131 {
132         int res = 0, i;
133         SQLINTEGER nativeerror=0, numfields=0;
134         SQLSMALLINT diagbytes=0;
135         unsigned char state[10], diagnostic[256];
136
137         res = SQLExecute(stmt);
138         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
139                 if (res == SQL_ERROR) {
140                         SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
141                         for (i=0; i< numfields + 1; i++) {
142                                 SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
143                                 ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
144                                 if (i > 10) {
145                                         ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
146                                         break;
147                                 }
148                         }
149                 }
150 #if 0
151                 /* This is a really bad method of trying to correct a dead connection.  It
152                  * only ever really worked with MySQL.  It will not work with any other
153                  * database, since most databases prepare their statements on the server,
154                  * and if you disconnect, you invalidate the statement handle.  Hence, if
155                  * you disconnect, you're going to fail anyway, whether you try to execute
156                  * a second time or not.
157                  */
158                 ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
159                 ast_mutex_lock(&obj->lock);
160                 obj->up = 0;
161                 ast_mutex_unlock(&obj->lock);
162                 odbc_obj_disconnect(obj);
163                 odbc_obj_connect(obj);
164                 res = SQLExecute(stmt);
165 #endif
166         }
167         
168         return res;
169 }
170
171
172 int ast_odbc_sanity_check(struct odbc_obj *obj) 
173 {
174         char *test_sql = "select 1";
175         SQLHSTMT stmt;
176         int res = 0;
177
178         if (obj->parent->sanitysql)
179                 test_sql = obj->parent->sanitysql;
180
181         if (obj->up) {
182                 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
183                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
184                         obj->up = 0;
185                 } else {
186                         res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
187                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
188                                 obj->up = 0;
189                         } else {
190                                 res = SQLExecute(stmt);
191                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
192                                         obj->up = 0;
193                                 }
194                         }
195                 }
196                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
197         }
198
199         if (!obj->up) { /* Try to reconnect! */
200                 ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
201                 odbc_obj_disconnect(obj);
202                 odbc_obj_connect(obj);
203         }
204         return obj->up;
205 }
206
207 static int load_odbc_config(void)
208 {
209         static char *cfg = "res_odbc.conf";
210         struct ast_config *config;
211         struct ast_variable *v;
212         char *cat, *dsn, *username, *password, *sanitysql;
213         int enabled, pooling, limit;
214         int connect = 0, res = 0;
215
216         struct odbc_class *new;
217
218         config = ast_config_load(cfg);
219         if (!config) {
220                 ast_log(LOG_WARNING, "Unable to load config file res_odbc.conf\n");
221                 return -1;
222         }
223         for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
224                 if (!strcasecmp(cat, "ENV")) {
225                         for (v = ast_variable_browse(config, cat); v; v = v->next) {
226                                 setenv(v->name, v->value, 1);
227                                 ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
228                         }
229                 } else {
230                         /* Reset all to defaults for each class of odbc connections */
231                         dsn = username = password = sanitysql = NULL;
232                         enabled = 1;
233                         connect = 0;
234                         pooling = 0;
235                         limit = 0;
236                         for (v = ast_variable_browse(config, cat); v; v = v->next) {
237                                 if (!strcasecmp(v->name, "pooling")) {
238                                         if (ast_true(v->value))
239                                                 pooling = 1;
240                                 } else if (!strcasecmp(v->name, "limit")) {
241                                         sscanf(v->value, "%d", &limit);
242                                         if (ast_true(v->value) && !limit) {
243                                                 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
244                                                 limit = 1023;
245                                         } else if (ast_false(v->value)) {
246                                                 ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
247                                                 enabled = 0;
248                                                 break;
249                                         }
250                                 } else if (!strcasecmp(v->name, "enabled")) {
251                                         enabled = ast_true(v->value);
252                                 } else if (!strcasecmp(v->name, "pre-connect")) {
253                                         connect = ast_true(v->value);
254                                 } else if (!strcasecmp(v->name, "dsn")) {
255                                         dsn = v->value;
256                                 } else if (!strcasecmp(v->name, "username")) {
257                                         username = v->value;
258                                 } else if (!strcasecmp(v->name, "password")) {
259                                         password = v->value;
260                                 } else if (!strcasecmp(v->name, "sanitysql")) {
261                                         sanitysql = v->value;
262                                 }
263                         }
264
265                         if (enabled && !ast_strlen_zero(dsn)) {
266                                 new = ast_calloc(1, sizeof(*new));
267
268                                 if (!new) {
269                                         res = -1;
270                                         break;
271                                 }
272
273                                 if (cat)
274                                         ast_copy_string(new->name, cat, sizeof(new->name));
275                                 if (dsn)
276                                         ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
277                                 if (username)
278                                         ast_copy_string(new->username, username, sizeof(new->username));
279                                 if (password)
280                                         ast_copy_string(new->password, password, sizeof(new->password));
281                                 if (sanitysql)
282                                         ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql));
283
284                                 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
285                                 res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
286
287                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
288                                         ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
289                                         SQLFreeHandle(SQL_HANDLE_ENV, new->env);
290                                         return res;
291                                 }
292
293                                 if (pooling) {
294                                         new->haspool = pooling;
295                                         if (limit) {
296                                                 new->limit = limit;
297                                         } else {
298                                                 ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
299                                                 new->limit = 5;
300                                         }
301                                 }
302
303                                 odbc_register_class(new, connect);
304                                 ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
305                         }
306                 }
307         }
308         ast_config_destroy(config);
309         return res;
310 }
311
312 static int odbc_show_command(int fd, int argc, char **argv)
313 {
314         struct odbc_class *class;
315         struct odbc_obj *current;
316
317         AST_LIST_LOCK(&odbc_list);
318         AST_LIST_TRAVERSE(&odbc_list, class, list) {
319                 if ((argc == 2) || (argc == 3 && !strcmp(argv[2], "all")) || (!strcmp(argv[2], class->name))) {
320                         int count = 0;
321                         ast_cli(fd, "Name: %s\nDSN: %s\n", class->name, class->dsn);
322
323                         if (class->haspool) {
324                                 ast_cli(fd, "Pooled: yes\nLimit: %d\nConnections in use: %d\n", class->limit, class->count);
325
326                                 AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
327                                         ast_cli(fd, "  Connection %d: %s", ++count, current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
328                                 }
329                         } else {
330                                 /* Should only ever be one of these */
331                                 AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
332                                         ast_cli(fd, "Pooled: no\nConnected: %s\n", current->up && ast_odbc_sanity_check(current) ? "yes" : "no");
333                                 }
334                         }
335
336                                 ast_cli(fd, "\n");
337                 }
338         }
339         AST_LIST_UNLOCK(&odbc_list);
340
341         return 0;
342 }
343
344 static char show_usage[] =
345 "Usage: odbc show [<class>]\n"
346 "       List settings of a particular ODBC class.\n"
347 "       or, if not specified, all classes.\n";
348
349 static struct ast_cli_entry cli_odbc[] = {
350         { { "odbc", "show", NULL },
351         odbc_show_command, "List ODBC DSN(s)",
352         show_usage },
353 };
354
355 static int odbc_register_class(struct odbc_class *class, int connect)
356 {
357         struct odbc_obj *obj;
358         if (class) {
359                 AST_LIST_LOCK(&odbc_list);
360                 AST_LIST_INSERT_HEAD(&odbc_list, class, list);
361                 AST_LIST_UNLOCK(&odbc_list);
362
363                 if (connect) {
364                         /* Request and release builds a connection */
365                         obj = ast_odbc_request_obj(class->name, 0);
366                         ast_odbc_release_obj(obj);
367                 }
368
369                 return 0;
370         } else {
371                 ast_log(LOG_WARNING, "Attempted to register a NULL class?\n");
372                 return -1;
373         }
374 }
375
376 void ast_odbc_release_obj(struct odbc_obj *obj)
377 {
378         /* For pooled connections, this frees the connection to be
379          * reused.  For non-pooled connections, it does nothing. */
380         obj->used = 0;
381 }
382
383 struct odbc_obj *ast_odbc_request_obj(const char *name, int check)
384 {
385         struct odbc_obj *obj = NULL;
386         struct odbc_class *class;
387
388         AST_LIST_LOCK(&odbc_list);
389         AST_LIST_TRAVERSE(&odbc_list, class, list) {
390                 if (!strcmp(class->name, name))
391                         break;
392         }
393         AST_LIST_UNLOCK(&odbc_list);
394
395         if (!class)
396                 return NULL;
397
398         AST_LIST_LOCK(&class->odbc_obj);
399         if (class->haspool) {
400                 /* Recycle connections before building another */
401                 AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
402                         if (! obj->used) {
403                                 obj->used = 1;
404                                 break;
405                         }
406                 }
407
408                 if (!obj && (class->count < class->limit)) {
409                         class->count++;
410                         obj = ast_calloc(1, sizeof(*obj));
411                         if (!obj) {
412                                 AST_LIST_UNLOCK(&class->odbc_obj);
413                                 return NULL;
414                         }
415                         ast_mutex_init(&obj->lock);
416                         obj->parent = class;
417                         odbc_obj_connect(obj);
418                         AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list);
419                 }
420         } else {
421                 /* Non-pooled connection: multiple modules can use the same connection. */
422                 AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
423                         /* Non-pooled connection: if there is an entry, return it */
424                         break;
425                 }
426
427                 if (!obj) {
428                         /* No entry: build one */
429                         obj = ast_calloc(1, sizeof(*obj));
430                         if (!obj) {
431                                 AST_LIST_UNLOCK(&class->odbc_obj);
432                                 return NULL;
433                         }
434                         ast_mutex_init(&obj->lock);
435                         obj->parent = class;
436                         if (odbc_obj_connect(obj) == ODBC_FAIL) {
437                                 ast_log(LOG_WARNING, "Failed to connect\n");
438                                 ast_mutex_destroy(&obj->lock);
439                                 free(obj);
440                         } else {
441                                 AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list);
442                         }
443                 }
444         }
445         AST_LIST_UNLOCK(&class->odbc_obj);
446
447         if (obj && check) {
448                 ast_odbc_sanity_check(obj);
449         }
450         return obj;
451 }
452
453 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
454 {
455         int res;
456         ast_mutex_lock(&obj->lock);
457
458         res = SQLDisconnect(obj->con);
459
460         if (res == ODBC_SUCCESS) {
461                 ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
462         } else {
463                 ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n",
464                 obj->parent->name, obj->parent->dsn);
465         }
466         obj->up = 0;
467         ast_mutex_unlock(&obj->lock);
468         return ODBC_SUCCESS;
469 }
470
471 static odbc_status odbc_obj_connect(struct odbc_obj *obj)
472 {
473         int res;
474         SQLINTEGER err;
475         short int mlen;
476         unsigned char msg[200], stat[10];
477 #ifdef NEEDTRACE
478         SQLINTEGER enable = 1;
479         char *tracefile = "/tmp/odbc.trace";
480 #endif
481         ast_mutex_lock(&obj->lock);
482
483         res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con);
484
485         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
486
487                 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
488                 SQLFreeHandle(SQL_HANDLE_ENV, obj->parent->env);
489
490                 ast_mutex_unlock(&obj->lock);
491                 return ODBC_FAIL;
492         }
493         SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
494 #ifdef NEEDTRACE
495         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER);
496         SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile));
497 #endif
498
499         if (obj->up) {
500                 odbc_obj_disconnect(obj);
501                 ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
502         } else {
503                 ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
504         }
505
506         res = SQLConnect(obj->con,
507                    (SQLCHAR *) obj->parent->dsn, SQL_NTS,
508                    (SQLCHAR *) obj->parent->username, SQL_NTS,
509                    (SQLCHAR *) obj->parent->password, SQL_NTS);
510
511         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
512                 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
513                 ast_mutex_unlock(&obj->lock);
514                 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
515                 return ODBC_FAIL;
516         } else {
517                 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
518                 obj->up = 1;
519         }
520
521         ast_mutex_unlock(&obj->lock);
522         return ODBC_SUCCESS;
523 }
524
525 static int reload(void)
526 {
527         static char *cfg = "res_odbc.conf";
528         struct ast_config *config;
529         struct ast_variable *v;
530         char *cat, *dsn, *username, *password, *sanitysql;
531         int enabled, pooling, limit;
532         int connect = 0, res = 0;
533
534         struct odbc_class *new, *class;
535         struct odbc_obj *current;
536
537         /* First, mark all to be purged */
538         AST_LIST_LOCK(&odbc_list);
539         AST_LIST_TRAVERSE(&odbc_list, class, list) {
540                 class->delme = 1;
541         }
542
543         config = ast_config_load(cfg);
544         if (config) {
545                 for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
546                         if (!strcasecmp(cat, "ENV")) {
547                                 for (v = ast_variable_browse(config, cat); v; v = v->next) {
548                                         setenv(v->name, v->value, 1);
549                                         ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
550                                 }
551                         } else {
552                                 /* Reset all to defaults for each class of odbc connections */
553                                 dsn = username = password = sanitysql = NULL;
554                                 enabled = 1;
555                                 connect = 0;
556                                 pooling = 0;
557                                 limit = 0;
558                                 for (v = ast_variable_browse(config, cat); v; v = v->next) {
559                                         if (!strcasecmp(v->name, "pooling")) {
560                                                 pooling = 1;
561                                         } else if (!strcasecmp(v->name, "limit")) {
562                                                 sscanf(v->value, "%d", &limit);
563                                                 if (ast_true(v->value) && !limit) {
564                                                         ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
565                                                         limit = 1023;
566                                                 } else if (ast_false(v->value)) {
567                                                         ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
568                                                         enabled = 0;
569                                                         break;
570                                                 }
571                                         } else if (!strcasecmp(v->name, "enabled")) {
572                                                 enabled = ast_true(v->value);
573                                         } else if (!strcasecmp(v->name, "pre-connect")) {
574                                                 connect = ast_true(v->value);
575                                         } else if (!strcasecmp(v->name, "dsn")) {
576                                                 dsn = v->value;
577                                         } else if (!strcasecmp(v->name, "username")) {
578                                                 username = v->value;
579                                         } else if (!strcasecmp(v->name, "password")) {
580                                                 password = v->value;
581                                         } else if (!strcasecmp(v->name, "sanitysql")) {
582                                                 sanitysql = v->value;
583                                         }
584                                 }
585
586                                 if (enabled && !ast_strlen_zero(dsn)) {
587                                         /* First, check the list to see if it already exists */
588                                         AST_LIST_TRAVERSE(&odbc_list, class, list) {
589                                                 if (!strcmp(class->name, cat)) {
590                                                         class->delme = 0;
591                                                         break;
592                                                 }
593                                         }
594
595                                         if (class) {
596                                                 new = class;
597                                         } else {
598                                                 new = ast_calloc(1, sizeof(*new));
599                                         }
600
601                                         if (!new) {
602                                                 res = -1;
603                                                 break;
604                                         }
605
606                                         if (cat)
607                                                 ast_copy_string(new->name, cat, sizeof(new->name));
608                                         if (dsn)
609                                                 ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
610                                         if (username)
611                                                 ast_copy_string(new->username, username, sizeof(new->username));
612                                         if (password)
613                                                 ast_copy_string(new->password, password, sizeof(new->password));
614                                         if (sanitysql)
615                                                 ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql));
616
617                                         if (!class) {
618                                                 SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
619                                                 res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
620
621                                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
622                                                         ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
623                                                         SQLFreeHandle(SQL_HANDLE_ENV, new->env);
624                                                         AST_LIST_UNLOCK(&odbc_list);
625                                                         return res;
626                                                 }
627                                         }
628
629                                         if (pooling) {
630                                                 new->haspool = pooling;
631                                                 if (limit) {
632                                                         new->limit = limit;
633                                                 } else {
634                                                         ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
635                                                         new->limit = 5;
636                                                 }
637                                         }
638
639                                         if (class) {
640                                                 ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn);
641                                         } else {
642                                                 odbc_register_class(new, connect);
643                                                 ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
644                                         }
645                                 }
646                         }
647                 }
648                 ast_config_destroy(config);
649         }
650
651         /* Purge classes that we know can go away (pooled with 0, only) */
652         AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) {
653                 if (class->delme && class->haspool && class->count == 0) {
654                         AST_LIST_TRAVERSE_SAFE_BEGIN(&(class->odbc_obj), current, list) {
655                                 AST_LIST_REMOVE_CURRENT(&(class->odbc_obj), list);
656                                 odbc_obj_disconnect(current);
657                                 ast_mutex_destroy(&current->lock);
658                                 free(current);
659                         }
660                         AST_LIST_TRAVERSE_SAFE_END;
661
662                         AST_LIST_REMOVE_CURRENT(&odbc_list, list);
663                         free(class);
664                 }
665         }
666         AST_LIST_TRAVERSE_SAFE_END;
667         AST_LIST_UNLOCK(&odbc_list);
668
669         return 0;
670 }
671
672 static int unload_module(void)
673 {
674         /* Prohibit unloading */
675         return -1;
676 }
677
678 static int load_module(void)
679 {
680         if(load_odbc_config() == -1)
681                 return AST_MODULE_LOAD_DECLINE;
682         ast_cli_register_multiple(cli_odbc, sizeof(cli_odbc) / sizeof(struct ast_cli_entry));
683         ast_log(LOG_NOTICE, "res_odbc loaded.\n");
684         return 0;
685 }
686
687 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ODBC Resource",
688                 .load = load_module,
689                 .unload = unload_module,
690                 .reload = reload,
691                );