d8c61077255bcdc60a743cd6e23e711eedfa3de1
[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 //if error occured, free the previous error's text and create a new one
39    pstatus=sock->status;
40    if (sock->lasterror) free(sock->lasterror);
41    sock->lasterror=0;
42    sock->sql_code=isc_sqlcode(sock->status);
43    isc_interprete(msg,&pstatus);
44    p=strdup(msg);
45    msg[0]='.';msg[1]=' ';
46    while (isc_interprete(msg+2,&pstatus)) {
47      l=strlen(p);
48      p=(char *) realloc(p,l+strlen(msg)+2);
49      strcat(p,msg);
50    }
51    sock->lasterror=p;
52  } else {
53 //return empty (but not null) string if there are  no error
54   if (sock->lasterror) *sock->lasterror=0;
55   else sock->lasterror=strdup("");
56  }
57  return sock->sql_code;
58 }
59
60
61 void fb_set_tpb(rlm_sql_firebird_sock * sock, int count,...) {
62  int i;
63  va_list arg;
64  va_start(arg,count);
65  sock->tpb=(char *) malloc(count);
66  for (i=0; i<count; i++) {
67     sock->tpb[i]=(char ) va_arg(arg,int);
68  }
69  sock->tpb_len=count;
70 }
71
72
73 void fb_dpb_add_str(char **dpb, char name, char *value) {
74  int l;
75  if (!value) return;
76  l=strlen(value);
77
78  *(*dpb)++ = name;
79  *(*dpb)++ = (char ) l;
80  memmove(*dpb,value,l);
81  *dpb+=l;
82
83 }
84
85 void fb_free_sqlda(XSQLDA *sqlda) {
86  int i;
87  for (i=0; i<sqlda->sqld; i++) {
88   free(sqlda->sqlvar[i].sqldata);
89   free(sqlda->sqlvar[i].sqlind);
90  }
91  sqlda->sqld=0;
92 }
93
94 void fb_set_sqlda(XSQLDA *sqlda) {
95  int i;
96  for (i=0; i<sqlda->sqld; i++) {
97   if ((sqlda->sqlvar[i].sqltype & ~1)==SQL_VARYING)
98     sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen + sizeof(short));
99   else
100    sqlda->sqlvar[i].sqldata = (char*)malloc(sqlda->sqlvar[i].sqllen);
101
102   if (sqlda->sqlvar[i].sqltype & 1) sqlda->sqlvar[i].sqlind = (short*)calloc(sizeof(short),1);
103   else sqlda->sqlvar[i].sqlind = 0;
104  }
105 }
106
107
108 //Macro for NULLs check
109 #define IS_NULL(x) (x->sqltype & 1) && (*x->sqlind < 0)
110
111 //Structure to manage a SQL_VARYING Firebird's data types
112 typedef struct vary_fb {
113   short vary_length;
114   char vary_string[1];
115 } VARY;
116
117 //function fb_store_row based on fiebird's apifull example
118 void fb_store_row(rlm_sql_firebird_sock *sock) {
119  int dtype;
120  struct tm times;
121  ISC_QUAD bid;
122  int i;
123  XSQLVAR *var;
124  VARY * vary;
125
126 //assumed: id,username,attribute,value,op
127  if (sock->row_fcount<sock->sqlda_out->sqld)  {
128    i=sock->row_fcount;
129    sock->row_fcount=sock->sqlda_out->sqld;
130    sock->row=(char **) realloc(sock->row,sock->row_fcount*sizeof(char *));
131    sock->row_sizes=(int *) realloc(sock->row_sizes,sock->row_fcount*sizeof(int));
132    while(i<sock->row_fcount) {
133      sock->row[i]=0;
134      sock->row_sizes[i++]=0;
135    }
136  }
137
138 for (i=0, var=sock->sqlda_out->sqlvar; i<sock->sqlda_out->sqld; var++,i++) {
139 //Initial buffer size to store field's data is 256 bytes
140   if (sock->row_sizes[i]<256) {
141    sock->row[i]=(char *) realloc(sock->row[i],256);
142    sock->row_sizes[i]=256;
143   }
144
145  if (IS_NULL(var)) {
146   strcpy(sock->row[i],"NULL");
147   continue;
148  }
149  dtype=var->sqltype & ~1;
150  switch (dtype) {
151    case SQL_TEXT:
152            if (sock->row_sizes[i]<=var->sqllen) {
153             sock->row_sizes[i]=var->sqllen+1;
154             sock->row[i]=(char *) realloc(sock->row[i],sock->row_sizes[i]);
155            }
156            memmove(sock->row[i],var->sqldata,var->sqllen);
157            sock->row[i][var->sqllen]=0;
158            break;
159    case SQL_VARYING:
160            vary = (VARY*) var->sqldata;
161            if (sock->row_sizes[i]<=vary->vary_length) {
162             sock->row_sizes[i]=vary->vary_length+1;
163             sock->row[i]=(char *) realloc(sock->row[i],sock->row_sizes[i]);
164            }
165            memmove(sock->row[i],vary->vary_string,vary->vary_length);
166            sock->row[i][vary->vary_length] =0;
167            break;
168
169     case SQL_FLOAT:
170             snprintf(sock->row[i],sock->row_sizes[i], "%15g", *(float ISC_FAR *) (var->sqldata));
171             break;
172     case SQL_SHORT:
173     case SQL_LONG:
174     case SQL_INT64:
175                 {
176                 ISC_INT64       value = 0;
177                 short           field_width = 0;
178                 short           dscale = 0;
179                 char *p;
180                 p=sock->row[i];
181                 switch (dtype)
182                     {
183                     case SQL_SHORT:
184                         value = (ISC_INT64) *(short *) var->sqldata;
185                         field_width = 6;
186                         break;
187                     case SQL_LONG:
188                         value = (ISC_INT64) *(int *) var->sqldata;
189                         field_width = 11;
190                         break;
191                     case SQL_INT64:
192                         value = (ISC_INT64) *(ISC_INT64 *) var->sqldata;
193                         field_width = 21;
194                         break;
195                     }
196                 dscale = var->sqlscale;
197                 if (dscale < 0)
198                     {
199                     ISC_INT64   tens;
200                     short       j;
201
202                     tens = 1;
203                     for (j = 0; j > dscale; j--) tens *= 10;
204
205                     if (value >= 0)
206                         sprintf (p, "%*lld.%0*lld",
207                                 field_width - 1 + dscale,
208                                 (ISC_INT64) value / tens,
209                                 -dscale,
210                                 (ISC_INT64) value % tens);
211                     else if ((value / tens) != 0)
212                         sprintf (p, "%*lld.%0*lld",
213                                 field_width - 1 + dscale,
214                                 (ISC_INT64) (value / tens),
215                                 -dscale,
216                                 (ISC_INT64) -(value % tens));
217                     else
218                         sprintf (p, "%*s.%0*lld",
219                                 field_width - 1 + dscale,
220                                 "-0",
221                                 -dscale,
222                                 (ISC_INT64) -(value % tens));
223                     }
224                 else if (dscale)
225                     sprintf (p, "%*lld%0*d",
226                             field_width,
227                             (ISC_INT64) value,
228                             dscale, 0);
229                 else
230                     sprintf (p, "%*lld",
231                             field_width,
232                             (ISC_INT64) value);
233                 }
234                 break;
235
236
237     case SQL_DOUBLE: case SQL_D_FLOAT:
238             snprintf(sock->row[i],sock->row_sizes[i], "%24f", *(double ISC_FAR *) (var->sqldata));
239             break;
240
241     case SQL_TIMESTAMP:
242                 isc_decode_timestamp((ISC_TIMESTAMP ISC_FAR *)var->sqldata, &times);
243                 snprintf(sock->row[i],sock->row_sizes[i],"%04d-%02d-%02d %02d:%02d:%02d.%04d",
244                                 times.tm_year + 1900,
245                                 times.tm_mon+1,
246                                 times.tm_mday,
247                                 times.tm_hour,
248                                 times.tm_min,
249                                 times.tm_sec,
250                                 ((ISC_TIMESTAMP *)var->sqldata)->timestamp_time % 10000);
251                 break;
252
253     case SQL_TYPE_DATE:
254                 isc_decode_sql_date((ISC_DATE ISC_FAR *)var->sqldata, &times);
255                 snprintf(sock->row[i],sock->row_sizes[i], "%04d-%02d-%02d",
256                                 times.tm_year + 1900,
257                                 times.tm_mon+1,
258                                 times.tm_mday);
259                 break;
260
261     case SQL_TYPE_TIME:
262                 isc_decode_sql_time((ISC_TIME ISC_FAR *)var->sqldata, &times);
263                 snprintf(sock->row[i],sock->row_sizes[i], "%02d:%02d:%02d.%04d",
264                                 times.tm_hour,
265                                 times.tm_min,
266                                 times.tm_sec,
267                                 (*((ISC_TIME *)var->sqldata)) % 10000);
268                 break;
269
270     case SQL_BLOB:
271     case SQL_ARRAY:
272                 /* Print the blob id on blobs or arrays */
273                 bid = *(ISC_QUAD ISC_FAR *) var->sqldata;
274                 snprintf(sock->row[i],sock->row_sizes[i],"%08lx:%08lx", bid.gds_quad_high, bid.gds_quad_low);
275                 break;
276
277  } //END SWITCH
278 } //END FOR
279 }
280
281
282 //=================
283 int fb_init_socket(rlm_sql_firebird_sock *sock) {
284     memset(sock, 0, sizeof(*sock));
285     sock->sqlda_out = (XSQLDA ISC_FAR *) calloc(XSQLDA_LENGTH (5),1);
286     sock->sqlda_out->sqln = 5;
287     sock->sqlda_out->version =  SQLDA_VERSION1;
288     sock->sql_dialect=3;
289 #ifdef _PTHREAD_H
290     pthread_mutex_init (&sock->mut, NULL);
291     radlog(L_DBG,"Init mutex %p\n",&sock->mut);
292 #endif
293
294
295 //set tpb to read_committed/wait/no_rec_version
296     fb_set_tpb(sock,5,
297         isc_tpb_version3,
298         isc_tpb_wait,
299         isc_tpb_write,
300         isc_tpb_read_committed,
301         isc_tpb_no_rec_version);
302     if (!sock->tpb) return -1;
303     return 0;
304 }
305
306 int fb_connect(rlm_sql_firebird_sock * sock,SQL_CONFIG *config) {
307  char *p;
308  char * database;
309
310  sock->dpb_len=4;
311  if (config->sql_login) sock->dpb_len+=strlen(config->sql_login)+2;
312  if (config->sql_password) sock->dpb_len+=strlen(config->sql_password)+2;
313
314  sock->dpb=(char *) malloc(sock->dpb_len);
315  p=sock->dpb;
316
317  *sock->dpb++ = isc_dpb_version1;
318  *sock->dpb++ = isc_dpb_num_buffers;
319  *sock->dpb++ = 1;
320  *sock->dpb++ = 90;
321
322  fb_dpb_add_str(&sock->dpb,isc_dpb_user_name,config->sql_login);
323  fb_dpb_add_str(&sock->dpb,isc_dpb_password,config->sql_password);
324
325  sock->dpb=p;
326 // Check if database and server in the form of server:database.
327 // If config->sql_server contains ':', then config->sql_db
328 // parameter ignored
329  if (strchr(config->sql_server,':'))  database=strdup(config->sql_server);
330  else {
331 // Make database and server to be in the form of server:database
332   int ls=strlen(config->sql_server);
333   int ld=strlen(config->sql_db);
334   database=(char *) calloc(ls+ld+2,1);
335   strcpy(database,config->sql_server);
336   database[ls]=':';
337   memmove(database+ls+1,config->sql_db,ld);
338  }
339  isc_attach_database(sock->status, 0, database, &sock->dbh, sock->dpb_len, sock->dpb);
340  free(database);
341  return fb_lasterror(sock);
342 }
343
344
345 int fb_fetch(rlm_sql_firebird_sock *sock) {
346  long fetch_stat;
347  if (sock->statement_type!=isc_info_sql_stmt_select) return 100;
348  fetch_stat=isc_dsql_fetch(sock->status, &sock->stmt, SQL_DIALECT_V6, sock->sqlda_out);
349  if (fetch_stat) {
350    if (fetch_stat!=100L) fb_lasterror(sock);
351    else  sock->sql_code=0;
352  }
353  return fetch_stat;
354 }
355
356 int fb_prepare(rlm_sql_firebird_sock *sock,char *sqlstr) {
357  static char     stmt_info[] = { isc_info_sql_stmt_type };
358  char            info_buffer[128];
359  short l;
360
361  if (!sock->trh) {
362   isc_start_transaction(sock->status,&sock->trh,1,&sock->dbh,sock->tpb_len,sock->tpb);
363   if (!sock->trh) return -4;
364  }
365
366  fb_free_statement(sock);
367  if (!sock->stmt) {
368    isc_dsql_allocate_statement(sock->status, &sock->dbh, &sock->stmt);
369    if (!sock->stmt) return -1;
370  }
371
372  fb_free_sqlda(sock->sqlda_out);
373  isc_dsql_prepare(sock->status, &sock->trh, &sock->stmt, 0, sqlstr, sock->sql_dialect, sock->sqlda_out);
374  if (IS_ISC_ERROR(sock->status)) return -2;
375
376  if (sock->sqlda_out->sqln<sock->sqlda_out->sqld) {
377    sock->sqlda_out->sqln=sock->sqlda_out->sqld;
378    sock->sqlda_out = (XSQLDA ISC_FAR *) realloc(sock->sqlda_out, XSQLDA_LENGTH (sock->sqlda_out->sqld));
379    isc_dsql_describe(sock->status,&sock->stmt,SQL_DIALECT_V6,sock->sqlda_out);
380    if (IS_ISC_ERROR(sock->status)) return -3;
381  }
382
383 //get statement type
384  isc_dsql_sql_info(sock->status, &sock->stmt, sizeof (stmt_info), stmt_info,sizeof (info_buffer), info_buffer);
385  if (IS_ISC_ERROR(sock->status)) return -4;
386
387  l = (short) isc_vax_integer((char ISC_FAR *) info_buffer + 1, 2);
388  sock->statement_type = isc_vax_integer((char ISC_FAR *) info_buffer + 3, l);
389
390  if (sock->sqlda_out->sqld) fb_set_sqlda(sock->sqlda_out); //set out sqlda
391
392  return 0;
393 }
394
395
396 int fb_sql_query(rlm_sql_firebird_sock *sock,char *sqlstr) {
397  if (fb_prepare(sock,sqlstr)) return fb_lasterror(sock);
398  switch (sock->statement_type) {
399     case isc_info_sql_stmt_exec_procedure:
400          isc_dsql_execute2(sock->status, &sock->trh, &sock->stmt, SQL_DIALECT_V6,0,sock->sqlda_out);
401          break;
402     default:
403          isc_dsql_execute(sock->status, &sock->trh, &sock->stmt, SQL_DIALECT_V6,0);
404          break;
405  }
406  return fb_lasterror(sock);
407 }
408
409 int fb_affected_rows(rlm_sql_firebird_sock *sock) {
410  static char    count_info[] = {isc_info_sql_records};
411  char            info_buffer[128];
412  char *p ;
413  int affected_rows=-1;
414
415  if (!sock->stmt) return -1;
416
417  isc_dsql_sql_info(sock->status, &sock->stmt,
418     sizeof (count_info), count_info,sizeof (info_buffer), info_buffer);
419  if (IS_ISC_ERROR(sock->status)) return fb_lasterror(sock);
420
421  p=info_buffer+3;
422  while (*p != isc_info_end) {
423        p++;
424        short len = (short)isc_vax_integer(p,2);
425        p+=2;
426        affected_rows = isc_vax_integer(p,len);
427        if (affected_rows>0) break;
428        p += len;
429  }
430  return affected_rows;
431 }
432
433 int fb_close_cursor(rlm_sql_firebird_sock *sock) {
434  isc_dsql_free_statement(sock->status, &sock->stmt, DSQL_close);
435  return fb_lasterror(sock);
436 }
437
438 void fb_free_statement(rlm_sql_firebird_sock *sock) {
439  if (sock->stmt) {
440   isc_dsql_free_statement(sock->status, &sock->stmt, DSQL_drop);
441   sock->stmt=0;
442  }
443 }
444
445 int fb_rollback(rlm_sql_firebird_sock *sock) {
446     sock->sql_code=0;
447     if (sock->trh)  {
448        isc_rollback_transaction (sock->status,&sock->trh);
449 //       sock->in_use=0;
450 #ifdef _PTHREAD_H
451          pthread_mutex_unlock(&sock->mut);
452 #endif
453
454        if (IS_ISC_ERROR(sock->status)) {
455          return fb_lasterror(sock);
456        }
457     }
458     return sock->sql_code;
459 }
460
461 int fb_commit(rlm_sql_firebird_sock *sock) {
462     sock->sql_code=0;
463     if (sock->trh)  {
464        isc_commit_transaction (sock->status,&sock->trh);
465        if (IS_ISC_ERROR(sock->status)) {
466          fb_lasterror(sock);
467          radlog(L_ERR,"Fail to commit. Error: %s. Try to rollback.\n",sock->lasterror);
468          return fb_rollback(sock);
469        }
470     }
471 //    sock->in_use=0;
472 #ifdef _PTHREAD_H
473     pthread_mutex_unlock(&sock->mut);
474 #endif
475     return sock->sql_code;
476 }
477
478 int fb_disconnect(rlm_sql_firebird_sock *sock) {
479  if (sock->dbh) {
480    fb_free_statement(sock);
481    isc_detach_database(sock->status,&sock->dbh);
482    return fb_lasterror(sock);
483  }
484  return 0;
485 }
486
487 void fb_destroy_socket(rlm_sql_firebird_sock *sock) {
488  int i;
489  fb_commit(sock);
490  if (fb_disconnect(sock)) {
491   radlog(L_ERR,"Fatal. Fail to disconnect DB. Error :%s\n",sock->lasterror);
492  }
493 #ifdef _PTHREAD_H
494  pthread_mutex_destroy (&sock->mut);
495 #endif
496  for (i=0; i<sock->row_fcount;i++) free(sock->row[i]);
497  free(sock->row);free(sock->row_sizes);
498  fb_free_sqlda(sock->sqlda_out);
499  free(sock->sqlda_out);
500  free(sock->tpb);
501  free(sock->dpb);
502  if (sock->lasterror) free(sock->lasterror);
503  memset(sock,0,sizeof(rlm_sql_firebird_sock));
504 }