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