Reformatting in firebird
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_firebird / sql_fbapi.c
1 /*
2  * sql_fbapi.c Part of Firebird rlm_sql driver
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the Free Software
16  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  *
18  * Copyright 2006  The FreeRADIUS server project
19  * Copyright 2006  Vitaly Bodzhgua <vitaly@eastera.net>
20  */
21
22 #include <freeradius-devel/ident.h>
23 RCSID("$Id$")
24
25 #include "sql_fbapi.h"
26
27 #include <stdarg.h>
28
29 int fb_lasterror(rlm_sql_firebird_sock *sock) {
30         char msg[512+2];
31         int l;
32         ISC_LONG *pstatus;
33         char *p = 0;
34
35         sock->sql_code = 0;
36
37         if (IS_ISC_ERROR(sock->status)) {
38                 /*
39                  *      If error occured, free the previous error's text
40                  *      and create a new one.
41                  */
42                 pstatus = sock->status;
43                 if (sock->lasterror) {
44                         free(sock->lasterror);
45                 }
46                 
47                 sock->lasterror = 0;
48                 sock->sql_code = isc_sqlcode(sock->status);
49                 
50                 isc_interprete(msg,&pstatus);
51                 p = strdup(msg);
52                 
53                 msg[0] = '.';
54                 msg[1] = ' ';
55                 
56                 while (isc_interprete(msg + 2, &pstatus)) {
57                         l = strlen(p);
58                         p = realloc(p, l + strlen(msg) + 2);
59                         
60                         strcat(p, msg);
61                 }
62                 
63                 sock->lasterror=p;
64         } else {
65                 //return empty (but not null) string if there are  no error
66                 if (sock->lasterror) {
67                         *(sock->lasterror) = '\0';
68                 } else {
69                         sock->lasterror = strdup("");
70                 }
71         }
72         
73         return sock->sql_code;
74 }
75
76
77 void fb_set_tpb(rlm_sql_firebird_sock * sock, int count, ...) {
78         int i;
79         va_list arg;
80         
81         va_start(arg, count);
82         sock->tpb = malloc(count);
83         
84         for (i = 0; i < count; i++) {
85                 sock->tpb[i] = (char) va_arg(arg, int);
86         }
87         
88         sock->tpb_len=count;
89 }
90
91
92 void fb_dpb_add_str(char **dpb, char name, char *value) {
93         int l;
94         
95         if (!value) {
96                 return;
97         }
98         
99         l = strlen(value);
100
101         *(*dpb)++ = name;
102         *(*dpb)++ = (char) l;
103         
104         memmove(*dpb, value, l);
105         
106         *dpb += l;
107 }
108
109 void fb_free_sqlda(XSQLDA *sqlda) {
110         int i;
111         for (i = 0; i < sqlda->sqld; i++) {
112                 free(sqlda->sqlvar[i].sqldata);
113                 free(sqlda->sqlvar[i].sqlind);
114         }
115         sqlda->sqld = 0;
116 }
117
118 void fb_set_sqlda(XSQLDA *sqlda) {
119         int i;
120         
121         for (i = 0; i < sqlda->sqld; i++) {
122                 if ((sqlda->sqlvar[i].sqltype & ~1) == SQL_VARYING) {
123                         sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen + sizeof(short));
124                 } else {
125                         sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen);
126                 }
127                 
128                 if (sqlda->sqlvar[i].sqltype & 1) {
129                         sqlda->sqlvar[i].sqlind = (short*)calloc(sizeof(short),1);
130                 } else {
131                         sqlda->sqlvar[i].sqlind = 0;
132                 }
133         }
134 }
135
136
137 //Macro for NULLs check
138 #define IS_NULL(x) (x->sqltype & 1) && (*x->sqlind < 0)
139
140 //Structure to manage a SQL_VARYING Firebird's data types
141 typedef struct vary_fb {
142          short vary_length;
143          char vary_string[1];
144 } VARY;
145
146 //function fb_store_row based on fiebird's apifull example
147 void fb_store_row(rlm_sql_firebird_sock *sock) {
148         int dtype;
149         struct tm times;
150         ISC_QUAD bid;
151         int i;
152         XSQLVAR *var;
153         VARY * vary;
154
155         /* assumed: id,username,attribute,value,op */
156         if (sock->row_fcount<sock->sqlda_out->sqld)  {
157                 i=sock->row_fcount;
158                 sock->row_fcount=sock->sqlda_out->sqld;
159                 sock->row = (char **) realloc(sock->row, sock->row_fcount * sizeof(char *));
160                 sock->row_sizes = (int *) realloc(sock->row_sizes, sock->row_fcount * sizeof(int));
161                 
162                 while( i <sock->row_fcount) {
163                         sock->row[i] = 0;
164                         sock->row_sizes[i++] = 0;
165                 }
166         }
167
168         for (i=0, var=sock->sqlda_out->sqlvar; i<sock->sqlda_out->sqld; var++,i++) {
169                 /*
170                  *      Initial buffer size to store field's data is 256 bytes
171                  */
172                 if (sock->row_sizes[i]<256) {
173                         sock->row[i]=(char *) realloc(sock->row[i],256);
174                         sock->row_sizes[i]=256;
175                 }
176
177                 if (IS_NULL(var)) {
178                         strcpy(sock->row[i],"NULL");
179                         continue;
180                 }
181                 
182                 dtype = var->sqltype & ~1;
183                 
184                 switch (dtype) {
185                 case SQL_TEXT:
186                         if (sock->row_sizes[i]<=var->sqllen) {
187                                 sock->row_sizes[i] = var->sqllen + 1;
188                                 sock->row[i] = realloc(sock->row[i],
189                                                        sock->row_sizes[i]);
190                         }
191                         
192                         memmove(sock->row[i], var->sqldata, var->sqllen);
193                         sock->row[i][var->sqllen] = 0;
194
195                         break;
196                 case SQL_VARYING:
197                         vary = (VARY*) var->sqldata;
198                         if (sock->row_sizes[i] <= vary->vary_length) {
199                                 sock->row_sizes[i] = vary->vary_length+1;
200                                 sock->row[i] = realloc(sock->row[i],
201                                                        sock->row_sizes[i]);
202                         }
203                         memmove(sock->row[i],vary->vary_string,vary->vary_length);
204                         sock->row[i][vary->vary_length] = 0;
205                         
206                         break;
207
208                 case SQL_FLOAT:
209                         snprintf(sock->row[i], sock->row_sizes[i], "%15g",
210                                  *(float ISC_FAR *) (var->sqldata));
211                         break;
212                 case SQL_SHORT:
213                 case SQL_LONG:
214                 case SQL_INT64:
215                         {
216                                 ISC_INT64 value = 0;
217                                 short field_width = 0;
218                                 short dscale = 0;
219                                 char *p;
220                                 p=sock->row[i];
221                                 
222                                 switch (dtype)
223                                         {
224                                         case SQL_SHORT:
225                                                 value = (ISC_INT64) *(short *)var->sqldata;
226                                                 field_width = 6;
227                                                 break;
228                                         case SQL_LONG:
229                                                 value = (ISC_INT64) *(int *)var->sqldata;
230                                                 field_width = 11;
231                                                 break;
232                                         case SQL_INT64:
233                                                 value = (ISC_INT64) *(ISC_INT64 *)var->sqldata;
234                                                 field_width = 21;
235                                                 break;
236                                 }
237                                 dscale = var->sqlscale;
238                                 
239                                 if (dscale < 0) {
240                                         ISC_INT64 tens;
241                                         short j;
242
243                                         tens = 1;
244                                         for (j = 0; j > dscale; j--) {
245                                                 tens *= 10;
246                                         }
247                                         
248                                         if (value >= 0) {
249                                                 sprintf(p, "%*lld.%0*lld",
250                                                         field_width - 1 + dscale,
251                                                         (ISC_INT64) value / tens,
252                                                         -dscale,
253                                                         (ISC_INT64) value % tens);
254                                         } else if ((value / tens) != 0) {
255                                                 sprintf (p, "%*lld.%0*lld",
256                                                         field_width - 1 + dscale,
257                                                         (ISC_INT64) (value / tens),
258                                                         -dscale,
259                                                         (ISC_INT64) -(value % tens));
260                                         } else {
261                                                 sprintf(p, "%*s.%0*lld", field_width - 1 + dscale,
262                                                         "-0", -dscale, (ISC_INT64) - (value % tens));
263                                         }
264                                 } else if (dscale) {
265                                         sprintf(p, "%*lld%0*d", field_width,
266                                                 (ISC_INT64) value, dscale, 0);
267                                 } else {
268                                         sprintf(p, "%*lld", field_width,
269                                                 (ISC_INT64) value);
270                                 }
271                         }
272                         break;
273
274                 case SQL_D_FLOAT:
275                 case SQL_DOUBLE:
276                         snprintf(sock->row[i],sock->row_sizes[i], "%24f",
277                                  *(double ISC_FAR *) (var->sqldata));
278                         break;
279
280                 case SQL_TIMESTAMP:
281                         isc_decode_timestamp((ISC_TIMESTAMP ISC_FAR *)var->sqldata, &times);
282                         snprintf(sock->row[i],sock->row_sizes[i],"%04d-%02d-%02d %02d:%02d:%02d.%04d",
283                                  times.tm_year + 1900,
284                                  times.tm_mon+1,
285                                  times.tm_mday,
286                                  times.tm_hour,
287                                  times.tm_min,
288                                  times.tm_sec,
289                                  ((ISC_TIMESTAMP *)var->sqldata)->timestamp_time % 10000);
290                         break;
291
292                 case SQL_TYPE_DATE:
293                         isc_decode_sql_date((ISC_DATE ISC_FAR *)var->sqldata, &times);
294                         snprintf(sock->row[i],sock->row_sizes[i], "%04d-%02d-%02d",
295                                  times.tm_year + 1900,
296                                  times.tm_mon+1,
297                                  times.tm_mday);
298                         break;
299
300                 case SQL_TYPE_TIME:
301                         isc_decode_sql_time((ISC_TIME ISC_FAR *)var->sqldata, &times);
302                         snprintf(sock->row[i],sock->row_sizes[i], "%02d:%02d:%02d.%04d",
303                                  times.tm_hour,
304                                  times.tm_min,
305                                  times.tm_sec,
306                                  (*((ISC_TIME *)var->sqldata)) % 10000);
307                         break;
308
309                 case SQL_BLOB:
310                 case SQL_ARRAY:
311                         /* Print the blob id on blobs or arrays */
312                         bid = *(ISC_QUAD ISC_FAR *) var->sqldata;
313                         snprintf(sock->row[i],sock->row_sizes[i],"%08lx:%08lx", bid.gds_quad_high, bid.gds_quad_low);
314                         break;
315
316                 }
317         }
318 }
319
320 int fb_init_socket(rlm_sql_firebird_sock *sock) {
321         memset(sock, 0, sizeof(*sock));
322         sock->sqlda_out = (XSQLDA ISC_FAR *) calloc(XSQLDA_LENGTH (5),1);
323         sock->sqlda_out->sqln = 5;
324         sock->sqlda_out->version =  SQLDA_VERSION1;
325         sock->sql_dialect = 3;
326 #ifdef _PTHREAD_H
327         pthread_mutex_init (&sock->mut, NULL);
328         radlog(L_DBG,"Init mutex %p\n",&sock->mut);
329 #endif
330
331         /* 
332          *      Set tpb to read_committed/wait/no_rec_version
333          */
334         fb_set_tpb(sock, 5, isc_tpb_version3, isc_tpb_wait, isc_tpb_write,
335                    isc_tpb_read_committed, isc_tpb_no_rec_version);
336         if (!sock->tpb) {
337                 return -1;
338         }
339         
340         return 0;
341 }
342
343 int fb_connect(rlm_sql_firebird_sock * sock,rlm_sql_config_t *config) {
344         char *p;
345         char *database;
346
347         sock->dpb_len = 4;
348         if (config->sql_login) {
349                 sock->dpb_len+=strlen(config->sql_login) + 2;
350         }
351         
352         if (config->sql_password) {
353                 sock->dpb_len += strlen(config->sql_password) + 2;
354         }
355         
356         sock->dpb = (char *) malloc(sock->dpb_len);
357         p = sock->dpb;
358
359         *sock->dpb++ = isc_dpb_version1;
360         *sock->dpb++ = isc_dpb_num_buffers;
361         *sock->dpb++ = 1;
362         *sock->dpb++ = 90;
363
364         fb_dpb_add_str(&sock->dpb, isc_dpb_user_name, config->sql_login);
365         fb_dpb_add_str(&sock->dpb, isc_dpb_password, config->sql_password);
366
367         sock->dpb = p;
368         
369         /*
370          *      Check if database and server in the form of server:database.
371          *      If config->sql_server contains ':', then config->sql_db
372          *      parameter ignored.
373          */
374         if (strchr(config->sql_server,':')) {
375                 database=strdup(config->sql_server);
376         } else {
377                 /*
378                  *      Make database and server to be in the form
379                  *      of server:database
380                  */
381                 int ls = strlen(config->sql_server);
382                 int ld = strlen(config->sql_db);
383                 database=(char *) calloc(ls+ld+2,1);
384                 strcpy(database,config->sql_server);
385                 database[ls]=':';
386                 memmove(database+ls+1,config->sql_db,ld);
387         }
388         isc_attach_database(sock->status, 0, database, &sock->dbh,
389                             sock->dpb_len, sock->dpb);
390         free(database);
391         
392         return fb_lasterror(sock);
393 }
394
395
396 int fb_fetch(rlm_sql_firebird_sock *sock) {
397         long fetch_stat;
398         if (sock->statement_type!=isc_info_sql_stmt_select) {
399                 return 100;
400         }
401         
402         fetch_stat = isc_dsql_fetch(sock->status, &sock->stmt,
403                                     SQL_DIALECT_V6, sock->sqlda_out);
404         if (fetch_stat) {
405                 if (fetch_stat!=100L) {
406                         fb_lasterror(sock);
407                 } else {
408                         sock->sql_code=0;
409                 }
410         }
411         
412         return fetch_stat;
413 }
414
415 int fb_prepare(rlm_sql_firebird_sock *sock,char *sqlstr) {
416         static char stmt_info[] = { isc_info_sql_stmt_type };
417         char info_buffer[128];
418         short l;
419
420         if (!sock->trh) {
421                 isc_start_transaction(sock->status, &sock->trh, 1, &sock->dbh, 
422                                       sock->tpb_len,sock->tpb);
423                 if (!sock->trh) {
424                         return -4;
425                 }
426         }
427
428         fb_free_statement(sock);
429         if (!sock->stmt) {
430                 isc_dsql_allocate_statement(sock->status, &sock->dbh,
431                                             &sock->stmt);
432                 if (!sock->stmt) {
433                         return -1;
434                 }
435         }
436
437         fb_free_sqlda(sock->sqlda_out);
438         isc_dsql_prepare(sock->status, &sock->trh, &sock->stmt, 0, sqlstr,
439                          sock->sql_dialect, sock->sqlda_out);
440         if (IS_ISC_ERROR(sock->status)) {
441                 return -2;
442         }
443         
444         if (sock->sqlda_out->sqln<sock->sqlda_out->sqld) {
445                 sock->sqlda_out->sqln=sock->sqlda_out->sqld;
446                 sock->sqlda_out = (XSQLDA ISC_FAR *) realloc(sock->sqlda_out,
447                                                              XSQLDA_LENGTH(sock->sqlda_out->sqld));
448                 isc_dsql_describe(sock->status, &sock->stmt, SQL_DIALECT_V6,
449                                   sock->sqlda_out);
450                 
451                 if (IS_ISC_ERROR(sock->status)) {
452                         return -3;
453                 }
454         }
455         /*
456          *      Get statement type
457          */
458         isc_dsql_sql_info(sock->status, &sock->stmt, sizeof(stmt_info),
459                           stmt_info,sizeof(info_buffer), info_buffer);
460         if (IS_ISC_ERROR(sock->status)) return -4;
461
462         l = (short) isc_vax_integer((char ISC_FAR *) info_buffer + 1, 2);
463         sock->statement_type = isc_vax_integer((char ISC_FAR *) info_buffer + 3,
464                                                l);
465
466         if (sock->sqlda_out->sqld) {
467                 fb_set_sqlda(sock->sqlda_out); //set out sqlda
468         }
469         
470         return 0;
471 }
472
473
474 int fb_sql_query(rlm_sql_firebird_sock *sock,char *sqlstr) {
475         if (fb_prepare(sock,sqlstr)) {
476                 return fb_lasterror(sock);
477         }
478         
479         switch (sock->statement_type) {
480                 case isc_info_sql_stmt_exec_procedure:
481                         isc_dsql_execute2(sock->status, &sock->trh, &sock->stmt,
482                                           SQL_DIALECT_V6, 0, sock->sqlda_out);
483                         break;
484                 default:
485                         isc_dsql_execute(sock->status, &sock->trh, &sock->stmt,
486                                          SQL_DIALECT_V6, 0);
487                         break;
488         }
489         return fb_lasterror(sock);
490 }
491
492 int fb_affected_rows(rlm_sql_firebird_sock *sock) {
493         static char count_info[] = {isc_info_sql_records};
494         char info_buffer[128];
495         char *p ;
496         int affected_rows = -1;
497
498         if (!sock->stmt) return -1;
499
500         isc_dsql_sql_info(sock->status, &sock->stmt,
501                           sizeof (count_info), count_info,
502                           sizeof (info_buffer), info_buffer);
503                           
504         if (IS_ISC_ERROR(sock->status)) {
505                 return fb_lasterror(sock);
506         }
507         
508         p = info_buffer + 3;
509         while (*p != isc_info_end) {
510                 p++;
511                 short len = (short)isc_vax_integer(p, 2);
512                 p += 2;
513                 
514                 affected_rows = isc_vax_integer(p, len);
515                 if (affected_rows > 0) {
516                         break;
517                 }
518                 p += len;
519         }
520         return affected_rows;
521 }
522
523 int fb_close_cursor(rlm_sql_firebird_sock *sock) {
524         isc_dsql_free_statement(sock->status, &sock->stmt, DSQL_close);
525         
526         return fb_lasterror(sock);
527 }
528
529 void fb_free_statement(rlm_sql_firebird_sock *sock) {
530         if (sock->stmt) {
531                 isc_dsql_free_statement(sock->status, &sock->stmt, DSQL_drop);
532                 sock->stmt = 0;
533         }
534 }
535
536 int fb_rollback(rlm_sql_firebird_sock *sock) {
537         sock->sql_code = 0;
538         if (sock->trh)  {
539                 isc_rollback_transaction(sock->status, &sock->trh);
540 //              sock->in_use=0;
541 #ifdef _PTHREAD_H
542                 pthread_mutex_unlock(&sock->mut);
543 #endif
544
545                 if (IS_ISC_ERROR(sock->status)) {
546                         return fb_lasterror(sock);
547                 }
548         }
549         return sock->sql_code;
550 }
551
552 int fb_commit(rlm_sql_firebird_sock *sock) {
553         sock->sql_code=0;
554         if (sock->trh)  {
555                 isc_commit_transaction (sock->status,&sock->trh);
556                 if (IS_ISC_ERROR(sock->status)) {
557                         fb_lasterror(sock);
558                         radlog(L_ERR, "Fail to commit. Error: %s. Try to rollback.",
559                                sock->lasterror);
560                         return fb_rollback(sock);
561                 }
562         }
563 //      sock->in_use=0;
564 #ifdef _PTHREAD_H
565         pthread_mutex_unlock(&sock->mut);
566 #endif
567         return sock->sql_code;
568 }
569
570 int fb_disconnect(rlm_sql_firebird_sock *sock) {
571         if (sock->dbh) {
572                 fb_free_statement(sock);
573                 isc_detach_database(sock->status,&sock->dbh);
574                 return fb_lasterror(sock);
575         }
576         return 0;
577 }
578
579 void fb_destroy_socket(rlm_sql_firebird_sock *sock) {
580         int i;
581         fb_commit(sock);
582         
583         if (fb_disconnect(sock)) {
584                 radlog(L_ERR, "Fatal. Fail to disconnect DB. Error :%s\n",
585                        sock->lasterror);
586         }
587         
588 #ifdef _PTHREAD_H
589         pthread_mutex_destroy (&sock->mut);
590 #endif
591         for (i=0; i<sock->row_fcount;i++) {
592                 free(sock->row[i]);
593         }
594         
595         free(sock->row);free(sock->row_sizes);
596         fb_free_sqlda(sock->sqlda_out);
597         
598         free(sock->sqlda_out);
599         free(sock->tpb);
600         free(sock->dpb);
601         
602         if (sock->lasterror) {
603                 free(sock->lasterror);
604         }
605         
606         memset(sock,0,sizeof(rlm_sql_firebird_sock));
607 }