1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf_sql.h>
26
27 #ifdef HAVE_MYSQL_H
28 # include <mysql.h>
29 #elif defined(HAVE_MYSQL_MYSQL_H)
30 # include <mysql/mysql.h>
31 #endif
32
33 #ifdef HAVE_PGSQL_LIBPQ_FE_H
34 # include <pgsql/libpq-fe.h>
35 #elif defined(HAVE_LIBPQ_FE_H)
36 # include <libpq-fe.h>
37 #endif
38
39 /* CFEngine connectors for sql databases. Note that there are significant
40 differences in db admin functions in the various implementations. e.g.
41 sybase/mysql "use database, create database" not in postgres.
42 */
43
44 /*****************************************************************************/
45
46 #ifdef HAVE_LIBMYSQLCLIENT
47
48 typedef struct
49 {
50 MYSQL conn;
51 MYSQL_RES *res;
52 } DbMysqlConn;
53
54 /*****************************************************************************/
55
CfConnectMysqlDB(const char * host,const char * user,const char * password,const char * database)56 static DbMysqlConn *CfConnectMysqlDB(const char *host, const char *user, const char *password, const char *database)
57 {
58 DbMysqlConn *c;
59
60 Log(LOG_LEVEL_VERBOSE, "This is a MySQL database");
61
62 c = xcalloc(1, sizeof(DbMysqlConn));
63
64 mysql_init(&c->conn);
65
66 if (!mysql_real_connect(&c->conn, host, user, password, database, 0, NULL, 0))
67 {
68 Log(LOG_LEVEL_ERR, "Failed to connect to existing MySQL database '%s'", mysql_error(&c->conn));
69 free(c);
70 return NULL;
71 }
72
73 return c;
74 }
75
76 /*****************************************************************************/
77
CfCloseMysqlDB(DbMysqlConn * c)78 static void CfCloseMysqlDB(DbMysqlConn *c)
79 {
80 mysql_close(&c->conn);
81 free(c);
82 }
83
84 /*****************************************************************************/
85
CfNewQueryMysqlDb(CfdbConn * cfdb,const char * query)86 static void CfNewQueryMysqlDb(CfdbConn *cfdb, const char *query)
87 {
88 DbMysqlConn *mc = cfdb->data;
89
90 if (mysql_query(&mc->conn, query) != 0)
91 {
92 Log(LOG_LEVEL_INFO, "MySQL query failed '%s'. (mysql_query: %s)", query, mysql_error(&mc->conn));
93 }
94 else
95 {
96 mc->res = mysql_store_result(&mc->conn);
97
98 if (mc->res)
99 {
100 cfdb->result = true;
101 cfdb->maxcolumns = mysql_num_fields(mc->res);
102 cfdb->maxrows = mysql_num_rows(mc->res);
103 }
104 }
105 }
106
107 /*****************************************************************************/
108
CfFetchMysqlRow(CfdbConn * cfdb)109 static void CfFetchMysqlRow(CfdbConn *cfdb)
110 {
111 int i;
112 MYSQL_ROW thisrow;
113 DbMysqlConn *mc = cfdb->data;
114
115 if (cfdb->maxrows > 0)
116 {
117 thisrow = mysql_fetch_row(mc->res);
118
119 if (thisrow)
120 {
121 cfdb->rowdata = xmalloc(sizeof(char *) * cfdb->maxcolumns);
122
123 for (i = 0; i < cfdb->maxcolumns; i++)
124 {
125 cfdb->rowdata[i] = (char *) thisrow[i];
126 }
127 }
128 else
129 {
130 cfdb->rowdata = NULL;
131 }
132 }
133 }
134
135 /*****************************************************************************/
136
CfDeleteMysqlQuery(CfdbConn * cfdb)137 static void CfDeleteMysqlQuery(CfdbConn *cfdb)
138 {
139 DbMysqlConn *mc = cfdb->data;
140
141 if (mc->res)
142 {
143 mysql_free_result(mc->res);
144 mc->res = NULL;
145 }
146 }
147
148 #else
149
CfConnectMysqlDB(ARG_UNUSED const char * host,ARG_UNUSED const char * user,ARG_UNUSED const char * password,ARG_UNUSED const char * database)150 static void *CfConnectMysqlDB(ARG_UNUSED const char *host, ARG_UNUSED const char *user, ARG_UNUSED const char *password, ARG_UNUSED const char *database)
151 {
152 Log(LOG_LEVEL_INFO, "There is no MySQL support compiled into this version");
153 return NULL;
154 }
155
CfCloseMysqlDB(ARG_UNUSED void * c)156 static void CfCloseMysqlDB(ARG_UNUSED void *c)
157 {
158 }
159
CfNewQueryMysqlDb(ARG_UNUSED CfdbConn * cfdb,ARG_UNUSED const char * query)160 static void CfNewQueryMysqlDb(ARG_UNUSED CfdbConn *cfdb, ARG_UNUSED const char *query)
161 {
162 }
163
CfFetchMysqlRow(ARG_UNUSED CfdbConn * cfdb)164 static void CfFetchMysqlRow(ARG_UNUSED CfdbConn *cfdb)
165 {
166 }
167
CfDeleteMysqlQuery(ARG_UNUSED CfdbConn * cfdb)168 static void CfDeleteMysqlQuery(ARG_UNUSED CfdbConn *cfdb)
169 {
170 }
171
172 #endif
173
174 #if defined(HAVE_LIBPQ) &&\
175 (defined(HAVE_PGSQL_LIBPQ_FE_H) || defined(HAVE_LIBPQ_FE_H))
176
177 typedef struct
178 {
179 PGconn *conn;
180 PGresult *res;
181 } DbPostgresqlConn;
182
183 /*****************************************************************************/
184
CfConnectPostgresqlDB(const char * host,const char * user,const char * password,const char * database)185 static DbPostgresqlConn *CfConnectPostgresqlDB(const char *host,
186 const char *user, const char *password, const char *database)
187 {
188 DbPostgresqlConn *cfdb;
189 char format[CF_BUFSIZE];
190
191 Log(LOG_LEVEL_VERBOSE, "This is a PotsgreSQL database");
192
193 cfdb = xcalloc(1, sizeof(DbPostgresqlConn));
194
195 if (strcmp(host, "localhost") == 0)
196 {
197 /* Some authentication problem - ?? */
198 if (database)
199 {
200 snprintf(format, CF_BUFSIZE - 1, "dbname=%s user=%s password=%s", database, user, password);
201 }
202 else
203 {
204 snprintf(format, CF_BUFSIZE - 1, "user=%s password=%s", user, password);
205 }
206 }
207 else
208 {
209 if (database)
210 {
211 snprintf(format, CF_BUFSIZE - 1, "dbname=%s host=%s user=%s password=%s", database, host, user, password);
212 }
213 else
214 {
215 snprintf(format, CF_BUFSIZE - 1, "host=%s user=%s password=%s", host, user, password);
216 }
217 }
218
219 cfdb->conn = PQconnectdb(format);
220
221 if (PQstatus(cfdb->conn) == CONNECTION_BAD)
222 {
223 Log(LOG_LEVEL_ERR, "Failed to connect to existing PostgreSQL database. (PQconnectdb: %s)", PQerrorMessage(cfdb->conn));
224 free(cfdb);
225 return NULL;
226 }
227
228 return cfdb;
229 }
230
231 /*****************************************************************************/
232
CfClosePostgresqlDb(DbPostgresqlConn * c)233 static void CfClosePostgresqlDb(DbPostgresqlConn *c)
234 {
235 PQfinish(c->conn);
236 free(c);
237 }
238
239 /*****************************************************************************/
240
CfNewQueryPostgresqlDb(CfdbConn * cfdb,const char * query)241 static void CfNewQueryPostgresqlDb(CfdbConn *cfdb, const char *query)
242 {
243 DbPostgresqlConn *pc = cfdb->data;
244
245 pc->res = PQexec(pc->conn, query);
246
247 if (PQresultStatus(pc->res) != PGRES_COMMAND_OK && PQresultStatus(pc->res) != PGRES_TUPLES_OK)
248 {
249 Log(LOG_LEVEL_INFO, "PostgreSQL query '%s' failed. (PQExec: %s)", query, PQerrorMessage(pc->conn));
250 }
251 else
252 {
253 cfdb->result = true;
254 cfdb->maxcolumns = PQnfields(pc->res);
255 cfdb->maxrows = PQntuples(pc->res);
256 }
257 }
258
259 /*****************************************************************************/
260
CfFetchPostgresqlRow(CfdbConn * cfdb)261 static void CfFetchPostgresqlRow(CfdbConn *cfdb)
262 {
263 assert(cfdb != NULL);
264 DbPostgresqlConn *pc = cfdb->data;
265
266 if (cfdb->row < 0 || (unsigned int) cfdb->row >= cfdb->maxrows)
267 {
268 cfdb->rowdata = NULL;
269 return;
270 }
271
272 if (cfdb->maxrows > 0)
273 {
274 cfdb->rowdata = xmalloc(sizeof(char *) * cfdb->maxcolumns);
275 }
276
277 for (unsigned int i = 0; i < cfdb->maxcolumns; i++)
278 {
279 cfdb->rowdata[i] = PQgetvalue(pc->res, cfdb->row, i);
280 }
281 }
282
283 /*****************************************************************************/
284
CfDeletePostgresqlQuery(CfdbConn * cfdb)285 static void CfDeletePostgresqlQuery(CfdbConn *cfdb)
286 {
287 DbPostgresqlConn *pc = cfdb->data;
288
289 PQclear(pc->res);
290 }
291
292 /*****************************************************************************/
293
294 #else
295
CfConnectPostgresqlDB(ARG_UNUSED const char * host,ARG_UNUSED const char * user,ARG_UNUSED const char * password,ARG_UNUSED const char * database)296 static void *CfConnectPostgresqlDB(ARG_UNUSED const char *host, ARG_UNUSED const char *user, ARG_UNUSED const char *password, ARG_UNUSED const char *database)
297 {
298 Log(LOG_LEVEL_INFO, "There is no PostgreSQL support compiled into this version");
299 return NULL;
300 }
301
CfClosePostgresqlDb(ARG_UNUSED void * c)302 static void CfClosePostgresqlDb(ARG_UNUSED void *c)
303 {
304 }
305
CfNewQueryPostgresqlDb(ARG_UNUSED CfdbConn * cfdb,ARG_UNUSED const char * query)306 static void CfNewQueryPostgresqlDb(ARG_UNUSED CfdbConn *cfdb, ARG_UNUSED const char *query)
307 {
308 }
309
CfFetchPostgresqlRow(ARG_UNUSED CfdbConn * cfdb)310 static void CfFetchPostgresqlRow(ARG_UNUSED CfdbConn *cfdb)
311 {
312 }
313
CfDeletePostgresqlQuery(ARG_UNUSED CfdbConn * cfdb)314 static void CfDeletePostgresqlQuery(ARG_UNUSED CfdbConn *cfdb)
315 {
316 }
317
318 #endif
319
320 /*****************************************************************************/
321
CfConnectDB(CfdbConn * cfdb,DatabaseType dbtype,char * remotehost,char * dbuser,char * passwd,char * db)322 void CfConnectDB(CfdbConn *cfdb, DatabaseType dbtype, char *remotehost, char *dbuser, char *passwd, char *db)
323 {
324
325 cfdb->connected = false;
326 cfdb->type = dbtype;
327
328 /* If db == NULL, no database was specified, so we assume it has not been created yet. Need to
329 open a generic database and create */
330
331 if (db == NULL)
332 {
333 db = "no db specified";
334 }
335
336 Log(LOG_LEVEL_VERBOSE, "Connect to SQL database '%s', user '%s', host '%s', type %d", db, dbuser, remotehost,
337 dbtype);
338
339 switch (dbtype)
340 {
341 case DATABASE_TYPE_MYSQL:
342 cfdb->data = CfConnectMysqlDB(remotehost, dbuser, passwd, db);
343 break;
344
345 case DATABASE_TYPE_POSTGRES:
346 cfdb->data = CfConnectPostgresqlDB(remotehost, dbuser, passwd, db);
347 break;
348
349 default:
350 Log(LOG_LEVEL_VERBOSE, "There is no SQL database selected");
351 break;
352 }
353
354 if (cfdb->data)
355 cfdb->connected = true;
356 }
357
358 /*****************************************************************************/
359
CfCloseDB(CfdbConn * cfdb)360 void CfCloseDB(CfdbConn *cfdb)
361 {
362 if (!cfdb->connected)
363 {
364 return;
365 }
366
367 switch (cfdb->type)
368 {
369 case DATABASE_TYPE_MYSQL:
370 CfCloseMysqlDB(cfdb->data);
371 break;
372
373 case DATABASE_TYPE_POSTGRES:
374 CfClosePostgresqlDb(cfdb->data);
375 break;
376
377 default:
378 Log(LOG_LEVEL_VERBOSE, "There is no SQL database selected");
379 break;
380 }
381
382 cfdb->connected = false;
383 }
384
385 /*****************************************************************************/
386
CfVoidQueryDB(CfdbConn * cfdb,char * query)387 void CfVoidQueryDB(CfdbConn *cfdb, char *query)
388 {
389 if (!cfdb->connected)
390 {
391 return;
392 }
393
394 /* If we don't need to retrieve table entries...*/
395 CfNewQueryDB(cfdb, query);
396 CfDeleteQuery(cfdb);
397 }
398
399 /*****************************************************************************/
400
CfNewQueryDB(CfdbConn * cfdb,char * query)401 void CfNewQueryDB(CfdbConn *cfdb, char *query)
402 {
403 cfdb->result = false;
404 cfdb->row = 0;
405 cfdb->column = 0;
406 cfdb->rowdata = NULL;
407 cfdb->maxcolumns = 0;
408 cfdb->maxrows = 0;
409
410 Log(LOG_LEVEL_DEBUG, "Before query '%s' succeeded, maxrows %d, maxcolumns %d", query, cfdb->maxrows, cfdb->maxcolumns);
411
412 switch (cfdb->type)
413 {
414 case DATABASE_TYPE_MYSQL:
415 CfNewQueryMysqlDb(cfdb, query);
416 break;
417
418 case DATABASE_TYPE_POSTGRES:
419 CfNewQueryPostgresqlDb(cfdb, query);
420 break;
421
422 default:
423 Log(LOG_LEVEL_VERBOSE, "There is no SQL database selected");
424 break;
425 }
426
427 Log(LOG_LEVEL_DEBUG, "Query '%s' succeeded. maxrows %d, maxcolumns %d", query, cfdb->maxrows, cfdb->maxcolumns);
428 }
429
430 /*****************************************************************************/
431
CfFetchRow(CfdbConn * cfdb)432 char **CfFetchRow(CfdbConn *cfdb)
433 {
434 switch (cfdb->type)
435 {
436 case DATABASE_TYPE_MYSQL:
437 CfFetchMysqlRow(cfdb);
438 break;
439
440 case DATABASE_TYPE_POSTGRES:
441 CfFetchPostgresqlRow(cfdb);
442 break;
443
444 default:
445 Log(LOG_LEVEL_VERBOSE, "There is no SQL database selected");
446 break;
447 }
448
449 cfdb->row++;
450 return cfdb->rowdata;
451 }
452
453 /*****************************************************************************/
454
CfFetchColumn(CfdbConn * cfdb,int col)455 char *CfFetchColumn(CfdbConn *cfdb, int col)
456 {
457 assert(cfdb != NULL);
458 assert(col >= 0 && (unsigned int) col < cfdb->maxcolumns);
459 if (cfdb->rowdata)
460 {
461 return cfdb->rowdata[col];
462 }
463 else
464 {
465 return NULL;
466 }
467 }
468
469 /*****************************************************************************/
470
CfDeleteQuery(CfdbConn * cfdb)471 void CfDeleteQuery(CfdbConn *cfdb)
472 {
473 switch (cfdb->type)
474 {
475 case DATABASE_TYPE_MYSQL:
476 CfDeleteMysqlQuery(cfdb);
477 break;
478
479 case DATABASE_TYPE_POSTGRES:
480 CfDeletePostgresqlQuery(cfdb);
481 break;
482
483 default:
484 Log(LOG_LEVEL_VERBOSE, "There is no SQL database selected");
485 break;
486 }
487
488 if (cfdb->rowdata)
489 {
490 free(cfdb->rowdata);
491 cfdb->rowdata = NULL;
492 }
493 }
494