dcfc9f91f26b5926a046d6731fd2968e843088d4
[freeradius.git] / src / modules / rlm_sql / drivers / rlm_sql_unixodbc / sql_unixodbc.c
1 /*
2  * sql_unixodbc.c       unixODBC 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 2000  The FreeRADIUS server project
19  * Copyright 2000  Dmitri Ageev <d_ageev@ortcc.ru>
20  */
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include "radiusd.h"
25
26 #include <sqltypes.h>
27 #include "rlm_sql.h"
28
29 typedef struct rlm_sql_unixodbc_sock {
30         SQLHENV env_handle;
31         SQLHDBC dbc_handle;
32         SQLHSTMT stmt_handle;
33         SQL_ROW row;
34         void *conn;
35 } rlm_sql_unixodbc_sock;
36
37
38 #include <sql.h>
39 #include <sqlext.h>
40
41 /* Forward declarations */
42 static char *sql_error(SQLSOCK *sqlsocket, SQL_CONFIG *config); 
43 static int sql_free_result(SQLSOCK *sqlsocket, SQL_CONFIG *config); 
44 static int sql_affected_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config); 
45 static int sql_num_fields(SQLSOCK *sqlsocket, SQL_CONFIG *config); 
46
47
48 /*************************************************************************
49  *
50  *      Function: sql_init_socket
51  *
52  *      Purpose: Establish connection to the db
53  *
54  *************************************************************************/
55 static int sql_init_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
56     rlm_sql_unixodbc_sock *unixodbc_sock;
57     long err_handle;
58     
59         if (!sqlsocket->conn) {
60                 sqlsocket->conn = (rlm_sql_unixodbc_sock *)rad_malloc(sizeof(rlm_sql_unixodbc_sock));
61                 if (!sqlsocket->conn) {
62                         return -1;
63                 }
64         }
65         unixodbc_sock = sqlsocket->conn;
66         memset(unixodbc_sock, 0, sizeof(*unixodbc_sock));
67     
68     /* 1. Allocate environment handle and register version */
69     err_handle = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&unixodbc_sock->env_handle);
70     if (!SQL_SUCCEEDED(err_handle))
71     {
72         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate environment handle %s\n", sql_error(sqlsocket, config));
73         return -1;
74     }
75     err_handle = SQLSetEnvAttr(unixodbc_sock->env_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
76     if (!SQL_SUCCEEDED(err_handle))
77     {
78         radlog(L_ERR, "rlm_sql_unixodbc: Can't register ODBC version %s\n", sql_error(sqlsocket, config));
79         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
80         return -1;
81     }
82     /* 2. Allocate connection handle */
83     err_handle = SQLAllocHandle(SQL_HANDLE_DBC, unixodbc_sock->env_handle, &unixodbc_sock->dbc_handle);
84     if (!SQL_SUCCEEDED(err_handle))
85     {
86         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate connection handle %s\n", sql_error(sqlsocket, config));
87         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
88         return -1;
89     }
90     
91     /* 3. Connect to the datasource */
92     err_handle = SQLConnect(unixodbc_sock->dbc_handle,
93         (SQLCHAR*) config->sql_server, strlen(config->sql_server),
94         (SQLCHAR*) config->sql_login, strlen(config->sql_login),
95         (SQLCHAR*) config->sql_password, strlen(config->sql_password));
96     if (!SQL_SUCCEEDED(err_handle))
97     {
98         radlog(L_ERR, "rlm_sql_unixodbc: Connection failed %s\n", sql_error(sqlsocket, config));
99         SQLFreeHandle(SQL_HANDLE_DBC, unixodbc_sock->dbc_handle);
100         SQLFreeHandle(SQL_HANDLE_ENV, unixodbc_sock->env_handle);
101         return -1;
102     }
103     
104     /* 4. Allocate the statement */
105     err_handle = SQLAllocStmt(unixodbc_sock->dbc_handle, &unixodbc_sock->stmt_handle);
106     if (!SQL_SUCCEEDED(err_handle))
107     {
108         radlog(L_ERR, "rlm_sql_unixodbc: Can't allocate the statement %s\n", sql_error(sqlsocket, config));
109         return -1;
110     }
111     
112     return 0;
113 }
114         
115
116 /************************************************************************* 
117  *                                                                         
118  *      Function: sql_destroy_socket                                       
119  *                                                                         
120  *      Purpose: Free socket and private connection data                   
121  *                                                                         
122  *************************************************************************/
123 static int sql_destroy_socket(SQLSOCK *sqlsocket, SQL_CONFIG *config)
124 {
125         free(sqlsocket->conn);
126         sqlsocket->conn = NULL;
127         return 0;
128 }
129
130       
131 /*************************************************************************
132  *
133  *      Function: sql_query
134  *
135  *      Purpose: Issue a non-SELECT query (ie: update/delete/insert) to
136  *               the database.
137  *
138  *************************************************************************/
139 static int sql_query(SQLSOCK *sqlsocket, SQL_CONFIG *config, char *querystr) {
140     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
141     long err_handle;
142         
143     if (config->sqltrace)
144         radlog(L_DBG, "query:  %s", querystr);
145         
146     /* Executing query */
147     err_handle = SQLExecDirect(unixodbc_sock->stmt_handle, (SQLCHAR *)querystr, strlen(querystr));
148     if (!SQL_SUCCEEDED(err_handle))
149     {
150         radlog(L_ERR, "rlm_sql_unixodbc: '%s'\n", sql_error(sqlsocket, config));
151         return -1;
152     }
153     return 0;
154 }
155
156
157 /*************************************************************************
158  *
159  *      Function: sql_select_query
160  *
161  *      Purpose: Issue a select query to the database
162  *
163  *************************************************************************/
164 static int sql_select_query(SQLSOCK *sqlsocket, SQL_CONFIG *config, char *querystr) {
165     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
166     SQLINTEGER column, len;
167     int numfields;
168
169     if(sql_query(sqlsocket, config, querystr) < 0)
170         return -1;      
171
172     numfields=sql_num_fields(sqlsocket, config);
173     if(numfields < 0)
174         return -1;
175
176     /* Reserving memory for result */
177     unixodbc_sock->row = (char **) rad_malloc((numfields+1)*sizeof(char *));
178     unixodbc_sock->row[numfields] = NULL;
179
180     for(column=1; column<=numfields; column++) {
181         SQLColAttributes(unixodbc_sock->stmt_handle,((SQLUSMALLINT) column),SQL_COLUMN_LENGTH,NULL,0,NULL,&len);
182         unixodbc_sock->row[column-1] = (SQLCHAR*)rad_malloc((int)++len);
183         SQLBindCol(unixodbc_sock->stmt_handle, column, SQL_C_CHAR, (SQLCHAR *)unixodbc_sock->row[column-1], len, NULL);
184     }
185         return 0;
186 }
187
188
189 /*************************************************************************
190  *
191  *      Function: sql_store_result
192  *
193  *      Purpose: database specific store_result function. Returns a result
194  *               set for the query.
195  *
196  *************************************************************************/
197 static int sql_store_result(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
198   /* Not used */
199     return 0;
200 }
201
202
203 /*************************************************************************
204  *
205  *      Function: sql_num_fields
206  *
207  *      Purpose: database specific num_fields function. Returns number
208  *               of columns from query
209  *
210  *************************************************************************/
211 static int sql_num_fields(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
212     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
213     long err_handle;
214     int num_fields = 0;
215     
216     err_handle = SQLNumResultCols(unixodbc_sock->stmt_handle,(SQLSMALLINT *)&num_fields);
217     if (!SQL_SUCCEEDED(err_handle))
218     {
219         radlog(L_ERR, "rlm_sql_unixodbc: '%s'\n", sql_error(sqlsocket, config));
220         return -1;
221     }
222     return num_fields;
223 }
224
225
226 /*************************************************************************
227  *
228  *      Function: sql_num_rows
229  *
230  *      Purpose: database specific num_rows. Returns number of rows in
231  *               query
232  *
233  *************************************************************************/
234 static int sql_num_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
235     return sql_affected_rows(sqlsocket, config);
236 }
237
238
239 /*************************************************************************
240  *
241  *      Function: sql_fetch_row
242  *
243  *      Purpose: database specific fetch_row. Returns a SQL_ROW struct
244  *               with all the data for the query in 'sqlsocket->row'. Returns
245  *               0 on success, -1 on failure, SQL_DOWN if 'database is down'.
246  *
247  *************************************************************************/
248 static int sql_fetch_row(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
249     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
250
251     sqlsocket->row = NULL;
252
253     if(SQLFetch(unixodbc_sock->stmt_handle) == SQL_NO_DATA_FOUND)
254         return 0;
255
256     /* XXX Check if return suggests we should return error or SQL_DOWN */
257
258     sqlsocket->row = unixodbc_sock->row;
259     return 0;
260 }
261
262
263 /*************************************************************************
264  *
265  *      Function: sql_finish_select_query
266  *
267  *      Purpose: End the select query, such as freeing memory or result
268  *
269  *************************************************************************/
270 static int sql_finish_select_query(SQLSOCK * sqlsocket, SQL_CONFIG *config) {
271     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
272
273     sql_free_result(sqlsocket, config);
274     SQLFreeStmt(unixodbc_sock->stmt_handle, SQL_CLOSE);
275     return 0;
276 }
277
278 /*************************************************************************
279  *
280  *      Function: sql_finish_query
281  *
282  *      Purpose: End the query, such as freeing memory
283  *
284  *************************************************************************/
285 static int sql_finish_query(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
286   /* Not used */
287     return 0;
288 }
289
290 /*************************************************************************
291  *
292  *      Function: sql_free_result
293  *
294  *      Purpose: database specific free_result. Frees memory allocated
295  *               for a result set
296  *
297  *************************************************************************/
298 static int sql_free_result(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
299     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
300     int column, numfileds=sql_num_fields(sqlsocket, config);
301
302     /* Freeing reserved memory */
303     if(unixodbc_sock->row != NULL) {
304         for(column=0; column<numfileds; column++) {
305             if(unixodbc_sock->row[column] != NULL) {
306                 free(unixodbc_sock->row[column]);
307                 unixodbc_sock->row[column] = NULL;
308             }
309         }
310         free(unixodbc_sock->row);
311         unixodbc_sock->row = NULL;
312     }
313     return 0;
314 }
315
316 /*************************************************************************
317  *
318  *      Function: sql_close
319  *
320  *      Purpose: database specific close. Closes an open database
321  *               connection and cleans up any open handles.
322  *
323  *************************************************************************/
324 static int sql_close(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
325     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
326         
327     SQLFreeStmt(unixodbc_sock->stmt_handle, SQL_DROP);
328     SQLDisconnect(unixodbc_sock->dbc_handle);
329     SQLFreeConnect(unixodbc_sock->dbc_handle);
330     SQLFreeEnv(unixodbc_sock->env_handle);
331
332     return 0;
333 }
334
335 /*************************************************************************
336  *
337  *      Function: sql_error
338  *
339  *      Purpose: database specific error. Returns error associated with
340  *               connection
341  *
342  *************************************************************************/
343 static char *sql_error(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
344     SQLCHAR state[256] = "";
345     SQLCHAR error[256] = "";
346     SQLINTEGER errornum = 0;
347     SQLSMALLINT length = 255;
348     char *result;
349     
350     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
351
352     SQLError(
353         unixodbc_sock->env_handle,
354         unixodbc_sock->dbc_handle,
355         unixodbc_sock->stmt_handle,
356         state,
357         &errornum,
358         error,
359         256,
360         &length);
361         
362     result = (char*)rad_malloc(strlen(state)+1+strlen(error));
363     sprintf(result, "%s %s", state, error);
364     return result;
365 }
366
367 /*************************************************************************
368  *
369  *      Function: sql_affected_rows
370  *
371  *      Purpose: Return the number of rows affected by the query (update,
372  *               or insert)
373  *
374  *************************************************************************/
375 static int sql_affected_rows(SQLSOCK *sqlsocket, SQL_CONFIG *config) {
376     rlm_sql_unixodbc_sock *unixodbc_sock = sqlsocket->conn;
377     long err_handle;
378     int affected_rows;
379
380     err_handle = SQLRowCount(unixodbc_sock->stmt_handle, (SQLINTEGER *)&affected_rows);
381     if (!SQL_SUCCEEDED(err_handle))
382     {
383         radlog(L_ERR, "rlm_sql_unixodbc: '%s'\n", sql_error(sqlsocket, config));
384         return -1;
385     }
386     return affected_rows;
387 }
388
389
390 /* Exported to rlm_sql */
391 rlm_sql_module_t rlm_sql_unixodbc = {
392         "rlm_sql_unixodbc",
393         sql_init_socket,
394         sql_destroy_socket,
395         sql_query,
396         sql_select_query,
397         sql_store_result,
398         sql_num_fields,
399         sql_num_rows,
400         sql_fetch_row,
401         sql_free_result,
402         sql_error,
403         sql_close,
404         sql_finish_query,
405         sql_finish_select_query,
406         sql_affected_rows
407 };