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