1 /**
2  * libdbi database backend
3  *
4  * Part of Gammu project
5  *
6  * Copyright (c) 2009 - 2018 Michal Cihar <michal@cihar.com>
7  *
8  * Licensed under GNU GPL version 2 or later
9  */
10 
11 #include <gammu.h>
12 
13 #include <stdlib.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <time.h>
18 #include <assert.h>
19 #ifdef WIN32
20 #include <windows.h>
21 #ifndef __GNUC__
22 #pragma comment(lib, "libdbi.lib")
23 #endif
24 #endif
25 
26 #include "../core.h"
27 #include "sql.h"
28 
SMSDDBI_GetNumber(GSM_SMSDConfig * Config,SQL_result * res,unsigned int field)29 long long SMSDDBI_GetNumber(GSM_SMSDConfig * Config, SQL_result *res, unsigned int field)
30 {
31 	unsigned int type;
32 
33 	field++;
34 	type = dbi_result_get_field_type_idx(res->dbi, field);
35 
36 	switch (type) {
37 		case DBI_TYPE_INTEGER:
38 			type = dbi_result_get_field_attribs_idx(res->dbi, field);
39 			if ((type & DBI_INTEGER_SIZEMASK) == DBI_INTEGER_SIZE1) {
40 				return dbi_result_get_int_idx(res->dbi, field);
41 			} else if ((type & DBI_INTEGER_SIZEMASK) == DBI_INTEGER_SIZE2) {
42 				return dbi_result_get_int_idx(res->dbi, field);
43 			} else if ((type & DBI_INTEGER_SIZEMASK) == DBI_INTEGER_SIZE3) {
44 				return dbi_result_get_int_idx(res->dbi, field);
45 			} else if ((type & DBI_INTEGER_SIZEMASK) == DBI_INTEGER_SIZE4) {
46 				return dbi_result_get_int_idx(res->dbi, field);
47 			} else if ((type & DBI_INTEGER_SIZEMASK) == DBI_INTEGER_SIZE8) {
48 				return dbi_result_get_longlong_idx(res->dbi, field);
49 			}
50 			SMSD_Log(DEBUG_ERROR, Config, "Wrong integer field subtype from DBI: %d", type);
51 			return -1;
52 		case DBI_TYPE_DECIMAL:
53 			type = dbi_result_get_field_attribs_idx(res->dbi, field);
54 			if ((type & DBI_DECIMAL_SIZEMASK) == DBI_DECIMAL_SIZE4) {
55 				return dbi_result_get_int_idx(res->dbi, field);
56 			} else if ((type & DBI_DECIMAL_SIZEMASK) == DBI_DECIMAL_SIZE8) {
57 				return dbi_result_get_longlong_idx(res->dbi, field);
58 			}
59 			SMSD_Log(DEBUG_ERROR, Config, "Wrong decimal field subtype from DBI: %d", type);
60 			return -1;
61 #ifdef DBI_TYPE_XDECIMAL
62 		case DBI_TYPE_XDECIMAL:
63 			return dbi_result_get_as_longlong_idx(res->dbi, field);
64 #endif
65 		default:
66 			SMSD_Log(DEBUG_ERROR, Config, "Wrong field type for number (not INTEGER nor DECIMAL) from DBI: %d", type);
67 			return -1;
68 	}
69 }
70 
SMSDDBI_GetDate(GSM_SMSDConfig * Config,SQL_result * res,unsigned int field)71 time_t SMSDDBI_GetDate(GSM_SMSDConfig * Config, SQL_result *res, unsigned int field)
72 {
73 	unsigned int type;
74 	const char *date;
75 
76 	field++;
77 	type = dbi_result_get_field_type_idx(res->dbi, field);
78 
79 	switch (type) {
80 		case DBI_TYPE_INTEGER:
81 		case DBI_TYPE_DECIMAL:
82 #ifdef DBI_TYPE_XDECIMAL
83 		case DBI_TYPE_XDECIMAL:
84 #endif
85 			return SMSDDBI_GetNumber(Config, res, field);
86 		case DBI_TYPE_STRING:
87 			date = dbi_result_get_string_idx(res->dbi, field);
88 			return SMSDSQL_ParseDate(Config, date);
89 		case DBI_TYPE_DATETIME:
90 			return dbi_result_get_datetime_idx(res->dbi, field);
91 		case DBI_TYPE_ERROR:
92 		default:
93 			SMSD_Log(DEBUG_ERROR, Config, "Wrong field type for date from DBI: %d", type);
94 			return -1;
95 	}
96 }
97 
SMSDDBI_GetBool(GSM_SMSDConfig * Config,SQL_result * res,unsigned int field)98 gboolean SMSDDBI_GetBool(GSM_SMSDConfig * Config, SQL_result *res, unsigned int field)
99 {
100 	unsigned int type;
101 	const char *value;
102 	int num;
103 
104 	field++;
105 	type = dbi_result_get_field_type_idx(res->dbi, field);
106 
107 	switch (type) {
108 		case DBI_TYPE_INTEGER:
109 		case DBI_TYPE_DECIMAL:
110 #ifdef DBI_TYPE_XDECIMAL
111 		case DBI_TYPE_XDECIMAL:
112 #endif
113 			num = SMSDDBI_GetNumber(Config, res, field);
114 			if (num == -1) {
115 				return -1;
116 			} else if (num == 0) {
117 				return FALSE;
118 			} else {
119 				return TRUE;
120 			}
121 		case DBI_TYPE_STRING:
122 			value = dbi_result_get_string_idx(res->dbi, field);
123 			return GSM_StringToBool(value);
124 		case DBI_TYPE_ERROR:
125 		default:
126 			SMSD_Log(DEBUG_ERROR, Config, "Wrong field type for boolean from DBI: %d", type);
127 			return -1;
128 	}
129 }
130 
SMSDDBI_GetString(GSM_SMSDConfig * Config,SQL_result * res,unsigned int col)131 const char *SMSDDBI_GetString(GSM_SMSDConfig * Config, SQL_result *res, unsigned int col)
132 {
133 	return dbi_result_get_string_idx(res->dbi, col+1);
134 }
135 
SMSDDBI_LogError(GSM_SMSDConfig * Config)136 static void SMSDDBI_LogError(GSM_SMSDConfig * Config)
137 {
138 	int rc;
139 	const char *msg;
140 	rc = dbi_conn_error(Config->conn.dbi, &msg);
141 	if (rc == -1) {
142 		SMSD_Log(DEBUG_ERROR, Config, "Unknown DBI error!");
143 	} else {
144 		SMSD_Log(DEBUG_ERROR, Config, "DBI error %d: %s", rc, msg);
145 	}
146 }
147 
SMSDDBI_Callback(dbi_conn Conn,void * Config)148 void SMSDDBI_Callback(dbi_conn Conn, void *Config)
149 {
150 	SMSDDBI_LogError((GSM_SMSDConfig *) Config);
151 }
152 
153 /* Disconnects from a database */
SMSDDBI_Free(GSM_SMSDConfig * Config)154 void SMSDDBI_Free(GSM_SMSDConfig * Config)
155 {
156 	if (Config->conn.dbi != NULL) {
157 		dbi_conn_close(Config->conn.dbi);
158 		dbi_shutdown();
159 		Config->conn.dbi = NULL;
160 	}
161 }
162 
163 /* Connects to database */
SMSDDBI_Connect(GSM_SMSDConfig * Config)164 static GSM_Error SMSDDBI_Connect(GSM_SMSDConfig * Config)
165 {
166 	int rc;
167 	struct GSM_SMSDdbobj *db = Config->db;
168 
169 	rc = dbi_initialize(Config->driverspath);
170 
171 	if (rc == 0) {
172 		SMSD_Log(DEBUG_ERROR, Config, "DBI did not find any drivers, try using DriversPath option");
173 		dbi_shutdown();
174 		return ERR_DB_DRIVER;
175 	} else if (rc < 0) {
176 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to initialize!");
177 		return ERR_DB_DRIVER;
178 	}
179 
180 	Config->conn.dbi = dbi_conn_new(Config->driver);
181 	if (Config->conn.dbi == NULL) {
182 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to init %s driver!", Config->driver);
183 		dbi_shutdown();
184 		return ERR_DB_DRIVER;
185 	} else {
186 		SMSD_Log(DEBUG_SQL, Config, "Using DBI driver '%s'", dbi_driver_get_name(dbi_conn_get_driver(Config->conn.dbi)));
187 	}
188 
189 	dbi_conn_error_handler(Config->conn.dbi, SMSDDBI_Callback, Config);
190 
191 	if (dbi_conn_set_option(Config->conn.dbi, "sqlite_dbdir", Config->dbdir) != 0) {
192 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set sqlite_dbdir!");
193 		SMSDDBI_Free(Config);
194 		return ERR_DB_CONFIG;
195 	}
196 	if (dbi_conn_set_option(Config->conn.dbi, "sqlite3_dbdir", Config->dbdir) != 0) {
197 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set sqlite3_dbdir!");
198 		SMSDDBI_Free(Config);
199 		return ERR_DB_CONFIG;
200 	}
201 	if (dbi_conn_set_option(Config->conn.dbi, "host", Config->host) != 0) {
202 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set host!");
203 		SMSDDBI_Free(Config);
204 		return ERR_DB_CONFIG;
205 	}
206 	if (dbi_conn_set_option(Config->conn.dbi, "username", Config->user) != 0) {
207 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set username!");
208 		SMSDDBI_Free(Config);
209 		return ERR_DB_CONFIG;
210 	}
211 	if (dbi_conn_set_option(Config->conn.dbi, "password", Config->password) != 0) {
212 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set password!");
213 		SMSDDBI_Free(Config);
214 		return ERR_DB_CONFIG;
215 	}
216 	if (dbi_conn_set_option(Config->conn.dbi, "dbname", Config->database) != 0) {
217 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set dbname!");
218 		SMSDDBI_Free(Config);
219 		return ERR_DB_CONFIG;
220 	}
221 	if (dbi_conn_set_option(Config->conn.dbi, "encoding", "UTF-8") != 0) {
222 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to set encoding!");
223 		SMSDDBI_Free(Config);
224 		return ERR_DB_CONFIG;
225 	}
226 
227 	if (dbi_conn_connect(Config->conn.dbi) != 0) {
228 		SMSD_Log(DEBUG_ERROR, Config, "DBI failed to connect!");
229 		SMSDDBI_Free(Config);
230 		return ERR_DB_CONNECT;
231 	}
232 	Config->db = db;
233 	return ERR_NONE;
234 }
235 
SMSDDBI_Query(GSM_SMSDConfig * Config,const char * query,SQL_result * res)236 static GSM_Error SMSDDBI_Query(GSM_SMSDConfig * Config, const char *query, SQL_result * res)
237 {
238 	const char *msg;
239 	int rc;
240 
241 	res->dbi = NULL;
242 
243 	res->dbi = dbi_conn_query(Config->conn.dbi, query);
244 	if (res->dbi != NULL)
245 		return ERR_NONE;
246 
247 	SMSD_Log(DEBUG_INFO, Config, "SQL failed: %s", query);
248 	/* Black magic to decide whether we should bail out or attempt to retry */
249 	rc = dbi_conn_error(Config->conn.dbi, &msg);
250 	if (rc != -1) {
251 		SMSD_Log(DEBUG_INFO, Config, "SQL failure: %s", msg);
252 		if (strstr(msg, "syntax") != NULL) {
253 			return ERR_SQL;
254 		}
255 		if (strstr(msg, "violation") != NULL) {
256 			return ERR_SQL;
257 		}
258 		if (strstr(msg, "violates") != NULL) {
259 			return ERR_SQL;
260 		}
261 		if (strstr(msg, "SQL error") != NULL) {
262 			return ERR_SQL;
263 		}
264 		if (strstr(msg, "duplicate") != NULL) {
265 			return ERR_SQL;
266 		}
267 		if (strstr(msg, "unique") != NULL) {
268 			return ERR_SQL;
269 		}
270 		if (strstr(msg, "need to rewrite") != NULL) {
271 			return ERR_SQL;
272 		}
273 		if (strstr(msg, "locked") != NULL) {
274 			return ERR_DB_TIMEOUT;
275 		}
276 	}
277 	return ERR_DB_TIMEOUT;
278 }
279 
280 /* free sql results */
SMSDDBI_FreeResult(GSM_SMSDConfig * Config,SQL_result * res)281 void SMSDDBI_FreeResult(GSM_SMSDConfig * Config, SQL_result *res)
282 {
283 	dbi_result_free(res->dbi);
284 }
285 
286 /* set pointer to next row */
SMSDDBI_NextRow(GSM_SMSDConfig * Config,SQL_result * res)287 int SMSDDBI_NextRow(GSM_SMSDConfig * Config, SQL_result *res)
288 {
289 	return dbi_result_next_row(res->dbi);
290 }
291 /* quote strings */
SMSDDBI_QuoteString(GSM_SMSDConfig * Config,const char * string)292 char * SMSDDBI_QuoteString(GSM_SMSDConfig * Config, const char *string)
293 {
294 	char *encoded_text = NULL;
295 	dbi_conn_quote_string_copy(Config->conn.dbi, string, &encoded_text);
296 	return encoded_text;
297 }
298 /* LAST_INSERT_ID */
SMSDDBI_SeqID(GSM_SMSDConfig * Config,const char * id)299 unsigned long long SMSDDBI_SeqID(GSM_SMSDConfig * Config, const char *id)
300 {
301 	unsigned long long new_id;
302 	char buffer[100];
303 
304 	new_id = dbi_conn_sequence_last(Config->conn.dbi, NULL);
305 	if (new_id == 0) {
306 		new_id = dbi_conn_sequence_last(Config->conn.dbi, id);
307 		/* Need to escape for PostgreSQL */
308 		if (new_id == 0) {
309 			sprintf(buffer, "\"%s\"", id);
310 			new_id = dbi_conn_sequence_last(Config->conn.dbi, buffer);
311 		}
312 	}
313 	return new_id;
314 }
315 
SMSDDBI_AffectedRows(GSM_SMSDConfig * Config,SQL_result * res)316 unsigned long SMSDDBI_AffectedRows(GSM_SMSDConfig * Config, SQL_result *res)
317 {
318 	return dbi_result_get_numrows_affected(res->dbi);
319 }
320 
321 struct GSM_SMSDdbobj SMSDDBI = {
322 	SMSDDBI_Connect,
323 	SMSDDBI_Query,
324 	SMSDDBI_Free,
325 	SMSDDBI_FreeResult,
326 	SMSDDBI_NextRow,
327 	SMSDDBI_SeqID,
328 	SMSDDBI_AffectedRows,
329 	SMSDDBI_GetString,
330 	SMSDDBI_GetNumber,
331 	SMSDDBI_GetDate,
332 	SMSDDBI_GetBool,
333 	SMSDDBI_QuoteString,
334 };
335 
336 /* How should editor hadle tabs in this file? Add editor commands here.
337  * vim: noexpandtab sw=8 ts=8 sts=8:
338  */
339