fix typo in function name (issue #5437)
[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
23 /*
24  *
25  *
26  *
27  */
28
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/config.h"
41 #include "asterisk/options.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/cli.h"
45 #include "asterisk/lock.h"
46 #include "asterisk/res_odbc.h"
47 #define MAX_ODBC_HANDLES 25
48
49 struct odbc_list
50 {
51         char name[80];
52         odbc_obj *obj;
53         int used;
54 };
55
56 static struct odbc_list ODBC_REGISTRY[MAX_ODBC_HANDLES];
57
58
59 static void odbc_destroy(void)
60 {
61         int x = 0;
62
63         for (x = 0; x < MAX_ODBC_HANDLES; x++) {
64                 if (ODBC_REGISTRY[x].obj) {
65                         destroy_odbc_obj(&ODBC_REGISTRY[x].obj);
66                         ODBC_REGISTRY[x].obj = NULL;
67                 }
68         }
69 }
70
71 static odbc_obj *odbc_read(struct odbc_list *registry, const char *name)
72 {
73         int x = 0;
74         for (x = 0; x < MAX_ODBC_HANDLES; x++) {
75                 if (registry[x].used && !strcmp(registry[x].name, name)) {
76                         return registry[x].obj;
77                 }
78         }
79         return NULL;
80 }
81
82 static int odbc_write(struct odbc_list *registry, char *name, odbc_obj *obj)
83 {
84         int x = 0;
85         for (x = 0; x < MAX_ODBC_HANDLES; x++) {
86                 if (!registry[x].used) {
87                         ast_copy_string(registry[x].name, name, sizeof(registry[x].name));
88                         registry[x].obj = obj;
89                         registry[x].used = 1;
90                         return 1;
91                 }
92         }
93         return 0;
94 }
95
96 static void odbc_init(void)
97 {
98         int x = 0;
99         for (x = 0; x < MAX_ODBC_HANDLES; x++) {
100                 memset(&ODBC_REGISTRY[x], 0, sizeof(struct odbc_list));
101         }
102 }
103
104 static char *tdesc = "ODBC Resource";
105 /* internal stuff */
106
107 int odbc_smart_execute(odbc_obj *obj, SQLHSTMT stmt) 
108 {
109         int res = 0;
110         res = SQLExecute(stmt);
111         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
112                 ast_log(LOG_WARNING, "SQL Execute error! Attempting a reconnect...\n");
113                 ast_mutex_lock(&obj->lock);
114                 obj->up = 0;
115                 ast_mutex_unlock(&obj->lock);
116                 odbc_obj_disconnect(obj);
117                 odbc_obj_connect(obj);
118                 res = SQLExecute(stmt);
119         }
120         
121         return res;
122 }
123
124
125 int odbc_smart_direct_execute(odbc_obj *obj, SQLHSTMT stmt, char *sql) 
126 {
127         int res = 0;
128
129         res = SQLExecDirect (stmt, sql, SQL_NTS);
130         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
131                 ast_log(LOG_WARNING, "SQL Execute error! Attempting a reconnect...\n");
132                 ast_mutex_lock(&obj->lock);
133                 obj->up = 0;
134                 ast_mutex_unlock(&obj->lock);
135                 odbc_obj_disconnect(obj);
136                 odbc_obj_connect(obj);
137                 res = SQLExecDirect (stmt, sql, SQL_NTS);
138         }
139         
140         return res;
141 }
142
143 int odbc_sanity_check(odbc_obj *obj) 
144 {
145         char *test_sql = "select 1";
146         SQLHSTMT stmt;
147         int res = 0;
148         SQLLEN rowcount = 0;
149
150         ast_mutex_lock(&obj->lock);
151         if(obj->up) { /* so you say... let's make sure */
152                 res = SQLAllocHandle (SQL_HANDLE_STMT, obj->con, &stmt);
153                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
154                         obj->up = 0; /* Liar!*/
155                 } else {
156                         res = SQLPrepare(stmt, test_sql, SQL_NTS);
157                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
158                                 obj->up = 0; /* Liar!*/
159                         } else {
160                                 res = SQLExecute(stmt);
161                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
162                                         obj->up = 0; /* Liar!*/
163                                 } else {
164                                         res = SQLRowCount(stmt, &rowcount);
165                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
166                                                 obj->up = 0; /* Liar!*/
167                                         }
168                                 }
169                         }
170                 }
171                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
172         }
173         ast_mutex_unlock(&obj->lock);
174
175         if(!obj->up) { /* Try to reconnect! */
176                 ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
177                 odbc_obj_disconnect(obj);
178                 odbc_obj_connect(obj);
179         }
180         return obj->up;
181 }
182
183 static int load_odbc_config(void)
184 {
185         static char *cfg = "res_odbc.conf";
186         struct ast_config *config;
187         struct ast_variable *v;
188         char *cat, *dsn, *username, *password;
189         int enabled;
190         int connect = 0;
191         char *env_var;
192
193         odbc_obj *obj;
194
195         config = ast_config_load(cfg);
196         if (config) {
197                 for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
198                         if (!strcmp(cat, "ENV")) {
199                                 for (v = ast_variable_browse(config, cat); v; v = v->next) {
200                                         env_var = malloc(strlen(v->name) + strlen(v->value) + 2);
201                                         if (env_var) {
202                                                 sprintf(env_var, "%s=%s", v->name, v->value);
203                                                 ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
204                                                 putenv(env_var);
205                                                 free(env_var);
206                                         }
207                                 }
208
209                         cat = ast_category_browse(config, cat);
210                         }
211
212                         dsn = username = password = NULL;
213                         enabled = 1;
214                         connect = 0;
215                         for (v = ast_variable_browse(config, cat); v; v = v->next) {
216                                 if (!strcmp(v->name, "enabled"))
217                                         enabled = ast_true(v->value);
218                                 if (!strcmp(v->name, "pre-connect"))
219                                         connect = ast_true(v->value);
220                                 if (!strcmp(v->name, "dsn"))
221                                         dsn = v->value;
222                                 if (!strcmp(v->name, "username"))
223                                         username = v->value;
224                                 if (!strcmp(v->name, "password"))
225                                         password = v->value;
226                         }
227
228                         if (enabled && dsn) {
229                                 obj = new_odbc_obj(cat, dsn, username, password);
230                                 if (obj) {
231                                         register_odbc_obj(cat, obj);
232                                         ast_log(LOG_NOTICE, "registered database handle '%s' dsn->[%s]\n", cat, obj->dsn);
233                                         if (connect) {
234                                                 odbc_obj_connect(obj);
235                                         }
236                                 } else {
237                                         ast_log(LOG_WARNING, "Addition of obj %s failed.\n", cat);
238                                 }
239
240                         }
241                 }
242                 ast_config_destroy(config);
243         }
244         return 0;
245 }
246
247 int odbc_dump_fd(int fd, odbc_obj *obj)
248 {
249         /* make sure the connection is up before we lie to our master.*/
250         odbc_sanity_check(obj);
251         ast_cli(fd, "Name: %s\nDSN: %s\nConnected: %s\n\n", obj->name, obj->dsn, obj->up ? "yes" : "no");
252         return 0;
253 }
254
255 static int odbc_connect_usage(int fd)
256 {
257         ast_cli(fd, "usage odbc connect <DSN>\n");
258         return 0;
259 }
260
261 static int odbc_disconnect_usage(int fd)
262 {
263         ast_cli(fd, "usage odbc disconnect <DSN>\n");
264         return 0;
265 }
266
267 static int odbc_show_command(int fd, int argc, char **argv)
268 {
269         odbc_obj *obj;
270         int x = 0;
271         
272         if (!strcmp(argv[1], "show")) {
273                 if (!argv[2] || (argv[2] && !strcmp(argv[2], "all"))) {
274                         for (x = 0; x < MAX_ODBC_HANDLES; x++) {
275                                 if (!ODBC_REGISTRY[x].used)
276                                         break;
277                                 if (ODBC_REGISTRY[x].obj)
278                                         odbc_dump_fd(fd, ODBC_REGISTRY[x].obj);
279                         }
280                 } else {
281                         obj = odbc_read(ODBC_REGISTRY, argv[2]);
282                         if (obj)
283                                 odbc_dump_fd(fd, obj);
284                 }
285         }
286         return 0;
287 }
288
289 static int odbc_disconnect_command(int fd, int argc, char **argv)
290 {
291         odbc_obj *obj;
292         if (!strcmp(argv[1], "disconnect")) {
293                 if (!argv[2])
294                         return odbc_disconnect_usage(fd);
295
296                 obj = odbc_read(ODBC_REGISTRY, argv[2]);
297                 if (obj) {
298                         odbc_obj_disconnect(obj);
299                 }
300         } 
301         return 0;
302 }
303
304 static int odbc_connect_command(int fd, int argc, char **argv)
305 {
306         odbc_obj *obj;
307         if (!argv[1])
308                 return odbc_connect_usage(fd);
309
310         if (!strcmp(argv[1], "connect") || !strcmp(argv[1], "disconnect")) {
311                 if (!argv[2])
312                         return odbc_connect_usage(fd);
313
314                 obj = odbc_read(ODBC_REGISTRY, argv[2]);
315                 if (obj) {
316                         odbc_obj_connect(obj);
317                 }
318         }
319         return 0;
320 }
321
322
323 static char connect_usage[] =
324 "Usage: odbc connect <DSN>\n"
325 "       Connect to ODBC DSN\n";
326
327 static char disconnect_usage[] =
328 "Usage: odbc connect <DSN>\n"
329 "       Disconnect from ODBC DSN\n";
330
331 static char show_usage[] =
332 "Usage: odbc show {DSN}\n"
333 "       Show ODBC {DSN}\n"
334 "       Specifying DSN will show that DSN else, all DSNs are shown\n";
335
336 static struct ast_cli_entry odbc_connect_struct =
337         { { "odbc", "connect", NULL }, odbc_connect_command, "Connect to ODBC DSN", connect_usage };
338
339
340 static struct ast_cli_entry odbc_disconnect_struct =
341         { { "odbc", "disconnect", NULL }, odbc_disconnect_command, "Disconnect from ODBC DSN", disconnect_usage };
342
343 static struct ast_cli_entry odbc_show_struct =
344         { { "odbc", "show", NULL }, odbc_show_command, "Show ODBC DSN(s)", show_usage };
345
346 /* api calls */
347
348 int register_odbc_obj(char *name, odbc_obj *obj)
349 {
350         if (obj != NULL)
351                 return odbc_write(ODBC_REGISTRY, name, obj);
352         return 0;
353 }
354
355 odbc_obj *fetch_odbc_obj(const char *name, int check)
356 {
357         odbc_obj *obj = NULL;
358         if((obj = (odbc_obj *) odbc_read(ODBC_REGISTRY, name))) {
359                 if(check)
360                         odbc_sanity_check(obj);
361         }
362         return obj;
363 }
364
365 odbc_obj *new_odbc_obj(char *name, char *dsn, char *username, char *password)
366 {
367         static odbc_obj *new;
368
369         new = malloc(sizeof(odbc_obj));
370         if (!new)
371                 return NULL;
372         memset(new, 0, sizeof(odbc_obj));
373         new->env = SQL_NULL_HANDLE;
374
375         new->name = malloc(strlen(name) + 1);
376         if (new->name == NULL)
377                 return NULL;
378
379         new->dsn = malloc(strlen(dsn) + 1);
380         if (new->dsn == NULL)
381                 return NULL;
382
383         if (username) {
384                 new->username = malloc(strlen(username) + 1);
385                 if (new->username == NULL)
386                         return NULL;
387                 strcpy(new->username, username);
388         }
389
390         if (password) {
391                 new->password = malloc(strlen(password) + 1);
392                 if (new->password == NULL)
393                         return NULL;
394                 strcpy(new->password, password);
395         }
396
397         strcpy(new->name, name);
398         strcpy(new->dsn, dsn);
399         new->up = 0;
400         ast_mutex_init(&new->lock);
401         return new;
402 }
403
404 void destroy_odbc_obj(odbc_obj **obj)
405 {
406         odbc_obj_disconnect(*obj);
407
408         ast_mutex_lock(&(*obj)->lock);
409         SQLFreeHandle(SQL_HANDLE_STMT, (*obj)->stmt);
410         SQLFreeHandle(SQL_HANDLE_DBC, (*obj)->con);
411         SQLFreeHandle(SQL_HANDLE_ENV, (*obj)->env);
412
413         free((*obj)->name);
414         free((*obj)->dsn);
415         if ((*obj)->username)
416                 free((*obj)->username);
417         if ((*obj)->password)
418                 free((*obj)->password);
419         ast_mutex_unlock(&(*obj)->lock);
420         ast_mutex_destroy(&(*obj)->lock);
421         free(*obj);
422 }
423
424 odbc_status odbc_obj_disconnect(odbc_obj *obj)
425 {
426         int res;
427         ast_mutex_lock(&obj->lock);
428
429         res = SQLDisconnect(obj->con);
430
431
432         if (res == ODBC_SUCCESS) {
433                 ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->name, obj->dsn);
434         } else {
435                 ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n",
436                 obj->name, obj->dsn);
437         }
438         obj->up = 0;
439         ast_mutex_unlock(&obj->lock);
440         return ODBC_SUCCESS;
441 }
442
443 odbc_status odbc_obj_connect(odbc_obj *obj)
444 {
445         int res;
446         SQLINTEGER err;
447         short int mlen;
448         char msg[200], stat[10];
449
450         ast_mutex_lock(&obj->lock);
451
452         if (obj->env == SQL_NULL_HANDLE) {
453                 res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &obj->env);
454
455                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
456                         if (option_verbose > 3)
457                                 ast_log(LOG_WARNING, "res_odbc: Error AllocHandle\n");
458                         ast_mutex_unlock(&obj->lock);
459                         return ODBC_FAIL;
460                 }
461
462                 res = SQLSetEnvAttr(obj->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
463
464                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
465                         if (option_verbose > 3)
466                                 ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
467                         SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
468                         ast_mutex_unlock(&obj->lock);
469                         return ODBC_FAIL;
470                 }
471
472                 res = SQLAllocHandle(SQL_HANDLE_DBC, obj->env, &obj->con);
473
474                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
475
476                         if (option_verbose > 3)
477                                 ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res);
478                         SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
479
480                         ast_mutex_unlock(&obj->lock);
481                         return ODBC_FAIL;
482                 }
483                 SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0);
484         }
485         if(obj->up) {
486                 odbc_obj_disconnect(obj);
487                 ast_log(LOG_NOTICE,"Re-connecting %s\n", obj->name);
488         }
489
490         ast_log(LOG_NOTICE, "Connecting %s\n", obj->name);
491
492         res = SQLConnect(obj->con,
493                    (SQLCHAR *) obj->dsn, SQL_NTS,
494                    (SQLCHAR *) obj->username, SQL_NTS,
495                    (SQLCHAR *) obj->password, SQL_NTS);
496
497         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
498                 SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen);
499                 SQLFreeHandle(SQL_HANDLE_ENV, obj->env);
500                 ast_mutex_unlock(&obj->lock);
501                 ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg);
502                 return ODBC_FAIL;
503         } else {
504
505                 ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->name, obj->dsn);
506                 obj->up = 1;
507         }
508
509         ast_mutex_unlock(&obj->lock);
510         return ODBC_SUCCESS;
511 }
512
513 STANDARD_LOCAL_USER;
514
515 LOCAL_USER_DECL;
516
517 int unload_module(void)
518 {
519         STANDARD_HANGUP_LOCALUSERS;
520         odbc_destroy();
521         ast_cli_unregister(&odbc_disconnect_struct);
522         ast_cli_unregister(&odbc_connect_struct);
523         ast_cli_unregister(&odbc_show_struct);
524         ast_log(LOG_NOTICE, "res_odbc unloaded.\n");
525         return 0;
526 }
527
528 int load_module(void)
529 {
530         odbc_init();
531         load_odbc_config();
532         ast_cli_register(&odbc_disconnect_struct);
533         ast_cli_register(&odbc_connect_struct);
534         ast_cli_register(&odbc_show_struct);
535         ast_log(LOG_NOTICE, "res_odbc loaded.\n");
536         return 0;
537 }
538
539 char *description(void)
540 {
541         return tdesc;
542 }
543
544 int usecount(void)
545 {
546         int res;
547         STANDARD_USECOUNT(res);
548         return res;
549 }
550
551 char *key()
552 {
553         return ASTERISK_GPL_KEY;
554 }