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 <verify_databases.h>
26
27 #include <actuator.h>
28 #include <promises.h>
29 #include <files_names.h>
30 #include <conversion.h>
31 #include <attributes.h>
32 #include <string_lib.h>
33 #include <locks.h>
34 #include <cf_sql.h>
35 #include <rlist.h>
36 #include <policy.h>
37 #include <cf-agent-enterprise-stubs.h>
38 #include <eval_context.h>
39 #include <ornaments.h>
40 #include <misc_lib.h>
41
42 static int CheckDatabaseSanity(const Attributes *a, const Promise *pp);
43 static PromiseResult VerifySQLPromise(EvalContext *ctx, const Attributes *a, const Promise *pp);
44 static bool VerifyDatabasePromise(CfdbConn *cfdb, char *database, const Attributes *a);
45
46 static bool ValidateSQLTableName(const char *path, char *db, size_t db_size, char *table, size_t table_size);
47 static bool VerifyTablePromise(EvalContext *ctx, CfdbConn *cfdb, char *table_path, Rlist *columns, const Attributes *a, const Promise *pp, PromiseResult *result);
48 static void QueryTableColumns(char *s, char *db, char *table);
49 static bool NewSQLColumns(char *table, Rlist *columns, char ***name_table, char ***type_table, int **size_table,
50 int **done);
51 static void DeleteSQLColumns(char **name_table, char **type_table, int *size_table, int *done, int len);
52 static void CreateDBQuery(DatabaseType type, char *query);
53 static bool CreateTableColumns(CfdbConn *cfdb, char *table, Rlist *columns);
54 static bool CheckSQLDataType(char *type, char *ref_type, const Promise *pp);
55 static int TableExists(CfdbConn *cfdb, char *name);
56 static Rlist *GetSQLTables(CfdbConn *cfdb);
57 static void ListTables(int type, char *query);
58 static bool ValidateRegistryPromiser(char *s, const Promise *pp);
59 static bool CheckRegistrySanity(const Attributes *a, const Promise *pp);
60
61 /*****************************************************************************/
62
VerifyDatabasePromises(EvalContext * ctx,const Promise * pp)63 PromiseResult VerifyDatabasePromises(EvalContext *ctx, const Promise *pp)
64 {
65 PromiseBanner(ctx, pp);
66
67 Attributes a = GetDatabaseAttributes(ctx, pp);
68
69 if (!CheckDatabaseSanity(&a, pp))
70 {
71 return PROMISE_RESULT_FAIL;
72 }
73
74 if (strcmp(a.database.type, "sql") == 0)
75 {
76 return VerifySQLPromise(ctx, &a, pp);
77 }
78 else if (strcmp(a.database.type, "ms_registry") == 0)
79 {
80 #if defined(__MINGW32__)
81 return VerifyRegistryPromise(ctx, &a, pp);
82 #endif
83 return PROMISE_RESULT_NOOP;
84 }
85 else
86 {
87 ProgrammingError("Unknown database type '%s'", a.database.type);
88 }
89 }
90
91 /*****************************************************************************/
92 /* Level */
93 /*****************************************************************************/
94
VerifySQLPromise(EvalContext * ctx,const Attributes * a,const Promise * pp)95 static PromiseResult VerifySQLPromise(EvalContext *ctx, const Attributes *a, const Promise *pp)
96 {
97 assert(a != NULL);
98 char database[CF_MAXDBNAMESIZE];
99 char table[CF_MAXTABLENAMESIZE];
100 char db_path[CF_MAXVARSIZE];
101 // Should have room for both buffers + 1 dot minus 1 NUL byte:
102 nt_static_assert(sizeof(db_path) >= (sizeof(database) + sizeof(table)));
103 char *sp;
104 int count = 0;
105 CfdbConn cfdb;
106 CfLock thislock;
107 char lockname[CF_BUFSIZE];
108
109 snprintf(lockname, CF_BUFSIZE - 1, "db-%s", pp->promiser);
110
111 thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
112 if (thislock.lock == NULL)
113 {
114 return PROMISE_RESULT_SKIPPED;
115 }
116
117 database[0] = '\0';
118 table[0] = '\0';
119
120 for (sp = pp->promiser; *sp != '\0'; sp++)
121 {
122 if (strchr("./\\", *sp))
123 {
124 count++;
125 strlcpy(table, sp + 1, sizeof(table));
126 sscanf(pp->promiser, "%[^.\\/]", database);
127
128 if (strlen(database) == 0)
129 {
130 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
131 "SQL database promiser syntax should be of the form \"database.table\"");
132 PromiseRef(LOG_LEVEL_ERR, pp);
133 YieldCurrentLock(thislock);
134 return PROMISE_RESULT_FAIL;
135 }
136 }
137 }
138
139 PromiseResult result = PROMISE_RESULT_NOOP;
140 if (count > 1)
141 {
142 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "SQL database promiser syntax should be of the form \"database.table\"");
143 PromiseRef(LOG_LEVEL_ERR, pp);
144 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
145 }
146
147 if (strlen(database) == 0)
148 {
149 strlcpy(database, pp->promiser, sizeof(database));
150 }
151
152 if (a->database.operation == NULL)
153 {
154 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
155 "Missing database_operation in database promise");
156 PromiseRef(LOG_LEVEL_ERR, pp);
157 YieldCurrentLock(thislock);
158 return PROMISE_RESULT_FAIL;
159 }
160
161 if (strcmp(a->database.operation, "delete") == 0)
162 {
163 /* Just deal with one */
164 strcpy(a->database.operation, "drop");
165 }
166
167 /* Connect to the server */
168
169 CfConnectDB(&cfdb, a->database.db_server_type, a->database.db_server_host, a->database.db_server_owner,
170 a->database.db_server_password, database);
171
172 if (!cfdb.connected)
173 {
174 /* If we haven't said create then db should already exist */
175
176 if ((a->database.operation) && (strcmp(a->database.operation, "create") != 0))
177 {
178 Log(LOG_LEVEL_ERR, "Could not connect an existing database '%s' - check server configuration?", database);
179 PromiseRef(LOG_LEVEL_ERR, pp);
180 CfCloseDB(&cfdb);
181 YieldCurrentLock(thislock);
182 return PROMISE_RESULT_FAIL;
183 }
184 }
185
186 /* Check change of existential constraints */
187
188 if ((a->database.operation) && (strcmp(a->database.operation, "create") == 0))
189 {
190 CfConnectDB(&cfdb, a->database.db_server_type, a->database.db_server_host, a->database.db_server_owner,
191 a->database.db_server_password, a->database.db_connect_db);
192
193 if (!cfdb.connected)
194 {
195 Log(LOG_LEVEL_ERR, "Could not connect to the sql_db server for '%s'", database);
196 YieldCurrentLock(thislock);
197 return PROMISE_RESULT_FAIL;
198 }
199
200 /* Don't drop the db if we really want to drop a table */
201
202 if ((strlen(table) == 0) || ((strlen(table) > 0) && (strcmp(a->database.operation, "drop") != 0)))
203 {
204 VerifyDatabasePromise(&cfdb, database, a);
205 }
206
207 /* Close the database here to commit the change - might have to reopen */
208
209 CfCloseDB(&cfdb);
210 }
211
212 /* Now check the structure of the named table, if any */
213
214 if (strlen(table) == 0)
215 {
216 YieldCurrentLock(thislock);
217 return result;
218 }
219
220 CfConnectDB(&cfdb, a->database.db_server_type, a->database.db_server_host, a->database.db_server_owner,
221 a->database.db_server_password, database);
222
223 if (!cfdb.connected)
224 {
225 Log(LOG_LEVEL_INFO, "Database '%s' is not connected", database);
226 }
227 else
228 {
229 snprintf(db_path, sizeof(db_path), "%s.%s", database, table);
230
231 if (VerifyTablePromise(ctx, &cfdb, db_path, a->database.columns, a, pp, &result))
232 {
233 cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_NOOP, pp, a, "Table '%s' is as promised", db_path);
234 }
235 else
236 {
237 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Table '%s' is not as promised", db_path);
238 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
239 }
240
241 /* Finally check any row constraints on this table */
242
243 if (a->database.rows)
244 {
245 Log(LOG_LEVEL_INFO,
246 "Database row operations are not currently supported. Please contact cfengine with suggestions.");
247 }
248
249 CfCloseDB(&cfdb);
250 }
251
252 YieldCurrentLock(thislock);
253
254 return result;
255 }
256
VerifyDatabasePromise(CfdbConn * cfdb,char * database,const Attributes * a)257 static bool VerifyDatabasePromise(CfdbConn *cfdb, char *database, const Attributes *a)
258 {
259 assert(a != NULL);
260 char query[CF_BUFSIZE], name[CF_MAXVARSIZE];
261 int found = false;
262
263 Log(LOG_LEVEL_VERBOSE, "Verifying promised database");
264
265 if (!cfdb->connected)
266 {
267 Log(LOG_LEVEL_INFO, "Database '%s' is not connected", database);
268 return false;
269 }
270
271 CreateDBQuery(cfdb->type, query);
272
273 CfNewQueryDB(cfdb, query);
274
275 if (cfdb->maxcolumns < 1)
276 {
277 Log(LOG_LEVEL_ERR, "The schema did not promise the expected number of fields - got %d expected >= %d",
278 cfdb->maxcolumns, 1);
279 return false;
280 }
281
282 while (CfFetchRow(cfdb))
283 {
284 strlcpy(name, CfFetchColumn(cfdb, 0), CF_MAXVARSIZE);
285
286 Log(LOG_LEVEL_VERBOSE, "Discovered a database called '%s'", name);
287
288 if (strcmp(name, database) == 0)
289 {
290 found = true;
291 }
292 }
293
294 if (found)
295 {
296 Log(LOG_LEVEL_VERBOSE, "Database '%s' exists on this connection", database);
297 return true;
298 }
299 else
300 {
301 Log(LOG_LEVEL_VERBOSE, "Database '%s' does not seem to exist on this connection", database);
302 }
303
304 if ((a->database.operation) && (strcmp(a->database.operation, "drop") == 0))
305 {
306 if (((a->transaction.action) != cfa_warn) && (!DONTDO))
307 {
308 Log(LOG_LEVEL_VERBOSE, "Attempting to delete the database '%s'", database);
309 snprintf(query, CF_MAXVARSIZE - 1, "drop database %s", database);
310 CfVoidQueryDB(cfdb, query);
311 return cfdb->result;
312 }
313 else
314 {
315 Log(LOG_LEVEL_WARNING, "Need to delete the database '%s' but only a warning was promised", database);
316 return false;
317 }
318 }
319
320 if ((a->database.operation) && (strcmp(a->database.operation, "create") == 0))
321 {
322 if (((a->transaction.action) != cfa_warn) && (!DONTDO))
323 {
324 Log(LOG_LEVEL_VERBOSE, "Attempting to create the database '%s'", database);
325 snprintf(query, CF_MAXVARSIZE - 1, "create database %s", database);
326 CfVoidQueryDB(cfdb, query);
327 return cfdb->result;
328 }
329 else
330 {
331 Log(LOG_LEVEL_WARNING, "Need to create the database '%s' but only a warning was promised", database);
332 return false;
333 }
334 }
335
336 return false;
337 }
338
339 /*****************************************************************************/
340
CheckDatabaseSanity(const Attributes * a,const Promise * pp)341 static int CheckDatabaseSanity(const Attributes *a, const Promise *pp)
342 {
343 assert(a != NULL);
344 Rlist *rp;
345 int retval = true, commas = 0;
346
347 if ((a->database.type) && (strcmp(a->database.type, "ms_registry") == 0))
348 {
349 retval = CheckRegistrySanity(a, pp);
350 }
351 else if ((a->database.type) && (strcmp(a->database.type, "sql") == 0))
352 {
353 if ((strchr(pp->promiser, '.') == NULL) && (strchr(pp->promiser, '/') == NULL)
354 && (strchr(pp->promiser, '\\') == NULL))
355 {
356 if (a->database.columns)
357 {
358 Log(LOG_LEVEL_ERR, "Row values promised for an SQL table, but only the root database was promised");
359 retval = false;
360 }
361
362 if (a->database.rows)
363 {
364 Log(LOG_LEVEL_ERR, "Columns promised for an SQL table, but only the root database was promised");
365 retval = false;
366 }
367 }
368
369 if (a->database.db_server_host == NULL)
370 {
371 Log(LOG_LEVEL_ERR, "No server host is promised for connecting to the SQL server");
372 retval = false;
373 }
374
375 if (a->database.db_server_owner == NULL)
376 {
377 Log(LOG_LEVEL_ERR, "No database login user is promised for connecting to the SQL server");
378 retval = false;
379 }
380
381 if (a->database.db_server_password == NULL)
382 {
383 Log(LOG_LEVEL_ERR, "No database authentication password is promised for connecting to the SQL server");
384 retval = false;
385 }
386
387 for (rp = a->database.columns; rp != NULL; rp = rp->next)
388 {
389 commas = CountChar(RlistScalarValue(rp), ',');
390
391 if ((commas > 2) || (commas < 1))
392 {
393 Log(LOG_LEVEL_ERR, "SQL Column format should be NAME,TYPE[,SIZE]");
394 retval = false;
395 }
396 }
397
398 }
399
400 if ((a->database.operation)
401 && ((strcmp(a->database.operation, "delete") == 0) || (strcmp(a->database.operation, "drop") == 0)))
402 {
403 if (pp->comment == NULL)
404 {
405 Log(LOG_LEVEL_ERR,
406 "When specifying a delete/drop from an SQL database you must add a comment. Take a backup of the database before making this change. This is a highly destructive operation.");
407 retval = false;
408 }
409 }
410
411 return retval;
412 }
413
CheckRegistrySanity(const Attributes * a,const Promise * pp)414 static bool CheckRegistrySanity(const Attributes *a, const Promise *pp)
415 {
416 assert(a != NULL);
417 bool retval = true;
418
419 ValidateRegistryPromiser(pp->promiser, pp);
420
421 if ((a->database.operation) && (strcmp(a->database.operation, "create") == 0))
422 {
423 if (a->database.rows == NULL)
424 {
425 Log(LOG_LEVEL_INFO, "No row values promised for the MS registry database");
426 }
427
428 if (a->database.columns != NULL)
429 {
430 Log(LOG_LEVEL_ERR, "Columns are only used to delete promised values for the MS registry database");
431 retval = false;
432 }
433 }
434
435 if ((a->database.operation)
436 && ((strcmp(a->database.operation, "delete") == 0) || (strcmp(a->database.operation, "drop") == 0)))
437 {
438 if (a->database.columns == NULL)
439 {
440 Log(LOG_LEVEL_INFO, "No columns were promised deleted in the MS registry database");
441 }
442
443 if (a->database.rows != NULL)
444 {
445 Log(LOG_LEVEL_ERR, "Rows cannot be deleted in the MS registry database, only entire columns");
446 retval = false;
447 }
448 }
449
450 for (Rlist *rp = a->database.rows; rp != NULL; rp = rp->next)
451 {
452 if (CountChar(RlistScalarValue(rp), ',') != 2)
453 {
454 Log(LOG_LEVEL_ERR, "Registry row format should be NAME,REG_SZ,VALUE, not '%s'", RlistScalarValue(rp));
455 retval = false;
456 }
457 }
458
459 for (Rlist *rp = a->database.columns; rp != NULL; rp = rp->next)
460 {
461 if (CountChar(RlistScalarValue(rp), ',') > 0)
462 {
463 Log(LOG_LEVEL_ERR, "MS registry column format should be NAME only in deletion");
464 retval = false;
465 }
466 }
467
468 return retval;
469 }
470
ValidateRegistryPromiser(char * key,const Promise * pp)471 static bool ValidateRegistryPromiser(char *key, const Promise *pp)
472 {
473 static char *const valid[] = { "HKEY_CLASSES_ROOT", "HKEY_CURRENT_CONFIG",
474 "HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE", "HKEY_USERS", NULL
475 };
476 char root_key[CF_MAXVARSIZE];
477 char *sp;
478 int i;
479
480 /* First remove the root key */
481
482 strlcpy(root_key, key, CF_MAXVARSIZE );
483 sp = strchr(root_key, '\\');
484 if (sp == NULL)
485 {
486 Log(LOG_LEVEL_ERR, "Cannot locate '\\' in '%s'", root_key);
487 Log(LOG_LEVEL_ERR, "Failed validating registry promiser");
488 PromiseRef(LOG_LEVEL_ERR, pp);
489 return false;
490 }
491 *sp = '\0';
492
493 for (i = 0; valid[i] != NULL; i++)
494 {
495 if (strcmp(root_key, valid[i]) == 0)
496 {
497 return true;
498 }
499 }
500
501 Log(LOG_LEVEL_ERR, "Non-editable registry prefix '%s'", root_key);
502 PromiseRef(LOG_LEVEL_ERR, pp);
503 return false;
504 }
505
506 /*****************************************************************************/
507 /* Linker troubles require this code to be here in the main body */
508 /*****************************************************************************/
509
VerifyTablePromise(EvalContext * ctx,CfdbConn * cfdb,char * table_path,Rlist * columns,const Attributes * a,const Promise * pp,PromiseResult * result)510 static bool VerifyTablePromise(EvalContext *ctx, CfdbConn *cfdb, char *table_path, Rlist *columns, const Attributes *a,
511 const Promise *pp, PromiseResult *result)
512 {
513 assert(a != NULL);
514 char name[CF_MAXVARSIZE], type[CF_MAXVARSIZE], query[CF_MAXVARSIZE];
515 char db[CF_MAXDBNAMESIZE];
516 char table[CF_MAXTABLENAMESIZE];
517 int i, count, size, no_of_cols, *size_table, *done, identified;
518 bool retval = true;
519 char **name_table, **type_table;
520
521 Log(LOG_LEVEL_VERBOSE, "Verifying promised table structure for '%s'", table_path);
522
523 if (!ValidateSQLTableName(table_path, db, sizeof(db), table, sizeof(table)))
524 {
525 Log(LOG_LEVEL_ERR,
526 "The structure of the promiser did not match that for an SQL table, i.e. 'database.table'");
527 return false;
528 }
529 else
530 {
531 Log(LOG_LEVEL_VERBOSE, "Assuming database '%s' with table '%s'", db, table);
532 }
533
534 /* Verify the existence of the tables within the database */
535
536 if (!TableExists(cfdb, table))
537 {
538 Log(LOG_LEVEL_ERR, "The database did not contain the promised table '%s'", table_path);
539
540 if ((a->database.operation) && (strcmp(a->database.operation, "create") == 0))
541 {
542 if ((!DONTDO) && ((a->transaction.action) != cfa_warn))
543 {
544 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_CHANGE, pp, a, "Database.table '%s' doesn't seem to exist, creating",
545 table_path);
546 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
547 return CreateTableColumns(cfdb, table, columns);
548 }
549 else
550 {
551 Log(LOG_LEVEL_WARNING, "Database.table '%s' doesn't seem to exist, but only a warning was promised",
552 table_path);
553 }
554 }
555
556 return false;
557 }
558
559 /* Get a list of the columns in the table */
560
561 QueryTableColumns(query, db, table);
562 CfNewQueryDB(cfdb, query);
563
564 if (cfdb->maxcolumns != 3)
565 {
566 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Could not make sense of the columns");
567 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
568 CfDeleteQuery(cfdb);
569 return false;
570 }
571
572 /* Assume that the Rlist has been validated and consists of a,b,c */
573
574 count = 0;
575 no_of_cols = RlistLen(columns);
576
577 if (!NewSQLColumns(table, columns, &name_table, &type_table, &size_table, &done))
578 {
579 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Could not make sense of the columns");
580 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
581 return false;
582 }
583
584 /* Obtain columns from the named table - if any */
585
586 while (CfFetchRow(cfdb))
587 {
588 char *sizestr;
589
590 name[0] = '\0';
591 type[0] = '\0';
592 size = CF_NOINT;
593
594 strlcpy(name, CfFetchColumn(cfdb, 0), CF_MAXVARSIZE);
595 strlcpy(type, CfFetchColumn(cfdb, 1), CF_MAXVARSIZE);
596 ToLowerStrInplace(type);
597 sizestr = CfFetchColumn(cfdb, 2);
598
599 if (sizestr)
600 {
601 size = IntFromString(sizestr);
602 }
603
604 Log(LOG_LEVEL_VERBOSE, "Discovered database column (%s,%s,%d)", name, type, size);
605
606 if (sizestr && (size == CF_NOINT))
607 {
608 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a,
609 "Integer size of SQL datatype could not be determined or was not specified - invalid promise.");
610 DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols);
611 CfDeleteQuery(cfdb);
612 return false;
613 }
614
615 identified = false;
616
617 for (i = 0; i < no_of_cols; i++)
618 {
619 if (done[i])
620 {
621 continue;
622 }
623
624 if (strcmp(name, name_table[i]) == 0)
625 {
626 CheckSQLDataType(type, type_table[i], pp);
627
628 if (size != size_table[i])
629 {
630 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
631 "Promised column '%s' in database.table '%s' has a non-matching array size (%d != %d)",
632 name, table_path, size, size_table[i]);
633 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
634 }
635 else
636 {
637 Log(LOG_LEVEL_VERBOSE, "Promised column '%s' in database.table '%s' is as promised", name,
638 table_path);
639 }
640
641 count++;
642 done[i] = true;
643 identified = true;
644 break;
645 }
646 }
647
648 if (!identified)
649 {
650 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
651 "Column '%s' found in database.table '%s' is not part of its promise.", name, table_path);
652 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
653
654 if ((a->database.operation) && (strcmp(a->database.operation, "drop") == 0))
655 {
656 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
657 "CFEngine will not promise to repair this, as the operation is potentially too destructive.");
658 // Future allow deletion?
659 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
660 }
661
662 retval = false;
663 }
664 }
665
666 CfDeleteQuery(cfdb);
667
668 /* Now look for deviations - only if we have promised to create missing */
669
670 if ((a->database.operation) && (strcmp(a->database.operation, "drop") == 0))
671 {
672 return retval;
673 }
674
675 if (count != no_of_cols)
676 {
677 for (i = 0; i < no_of_cols; i++)
678 {
679 if (!done[i])
680 {
681 Log(LOG_LEVEL_ERR, "Promised column '%s' missing from database table '%s'", name_table[i],
682 pp->promiser);
683
684 if ((!DONTDO) && ((a->transaction.action) != cfa_warn))
685 {
686 if (size_table[i] > 0)
687 {
688 snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s(%d)", table, name_table[i],
689 type_table[i], size_table[i]);
690 }
691 else
692 {
693 snprintf(query, CF_MAXVARSIZE - 1, "ALTER TABLE %s ADD %s %s", table, name_table[i],
694 type_table[i]);
695 }
696
697 CfVoidQueryDB(cfdb, query);
698 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_CHANGE, pp, a, "Adding promised column '%s' to database table '%s'",
699 name_table[i], table);
700 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
701 retval = true;
702 }
703 else
704 {
705 cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a,
706 "Promised column '%s' missing from database table '%s' but only a warning was promised",
707 name_table[i], table);
708 *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
709 retval = false;
710 }
711 }
712 }
713 }
714
715 DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols);
716
717 return retval;
718 }
719
720 /*****************************************************************************/
721
TableExists(CfdbConn * cfdb,char * name)722 static int TableExists(CfdbConn *cfdb, char *name)
723 {
724 Rlist *rp, *list = NULL;
725 int match = false;
726
727 list = GetSQLTables(cfdb);
728
729 for (rp = list; rp != NULL; rp = rp->next)
730 {
731 if (strcmp(name, RlistScalarValue(rp)) == 0)
732 {
733 match = true;
734 }
735 }
736
737 RlistDestroy(list);
738
739 return match;
740 }
741
742 /*****************************************************************************/
743
CreateTableColumns(CfdbConn * cfdb,char * table,Rlist * columns)744 static bool CreateTableColumns(CfdbConn *cfdb, char *table, Rlist *columns)
745 {
746 char entry[CF_MAXVARSIZE], query[CF_BUFSIZE];
747 int i, *size_table, *done;
748 char **name_table, **type_table;
749 int no_of_cols = RlistLen(columns);
750
751 Log(LOG_LEVEL_ERR, "Trying to create table '%s'", table);
752
753 if (!NewSQLColumns(table, columns, &name_table, &type_table, &size_table, &done))
754 {
755 return false;
756 }
757
758 if (no_of_cols > 0)
759 {
760 snprintf(query, CF_BUFSIZE - 1, "create table %s(", table);
761
762 for (i = 0; i < no_of_cols; i++)
763 {
764 Log(LOG_LEVEL_VERBOSE, "Forming column template %s %s %d", name_table[i], type_table[i],
765 size_table[i]);;
766
767 if (size_table[i] > 0)
768 {
769 snprintf(entry, CF_MAXVARSIZE - 1, "%s %s(%d)", name_table[i], type_table[i], size_table[i]);
770 }
771 else
772 {
773 snprintf(entry, CF_MAXVARSIZE - 1, "%s %s", name_table[i], type_table[i]);
774 }
775
776 strcat(query, entry);
777
778 if (i < no_of_cols - 1)
779 {
780 strcat(query, ",");
781 }
782 }
783
784 strcat(query, ")");
785 }
786
787 CfVoidQueryDB(cfdb, query);
788 DeleteSQLColumns(name_table, type_table, size_table, done, no_of_cols);
789 return true;
790 }
791
792 /*****************************************************************************/
793 /* Level */
794 /*****************************************************************************/
795
GetSQLTables(CfdbConn * cfdb)796 static Rlist *GetSQLTables(CfdbConn *cfdb)
797 {
798 Rlist *list = NULL;
799 char query[CF_MAXVARSIZE];
800
801 ListTables(cfdb->type, query);
802
803 CfNewQueryDB(cfdb, query);
804
805 if (cfdb->maxcolumns != 1)
806 {
807 Log(LOG_LEVEL_ERR, "Could not make sense of the columns");
808 CfDeleteQuery(cfdb);
809 return NULL;
810 }
811
812 while (CfFetchRow(cfdb))
813 {
814 RlistPrepend(&list, CfFetchColumn(cfdb, 0), RVAL_TYPE_SCALAR);
815 }
816
817 CfDeleteQuery(cfdb);
818
819 return list;
820 }
821
822 /*****************************************************************************/
823
CreateDBQuery(DatabaseType type,char * query)824 static void CreateDBQuery(DatabaseType type, char *query)
825 {
826 switch (type)
827 {
828 case DATABASE_TYPE_MYSQL:
829 snprintf(query, CF_MAXVARSIZE - 1, "show databases");
830 break;
831
832 case DATABASE_TYPE_POSTGRES:
833 /* This gibberish is the simplest thing I can find in postgres */
834
835 snprintf(query, CF_MAXVARSIZE - 1, "SELECT pg_database.datname FROM pg_database");
836 break;
837
838 default:
839 snprintf(query, CF_MAXVARSIZE, "NULL QUERY");
840 break;
841 }
842 }
843
844 /*****************************************************************************/
845
ValidateSQLTableName(const char * const path,char * const db,const size_t db_size,char * const table,const size_t table_size)846 static bool ValidateSQLTableName(
847 const char *const path,
848 char *const db,
849 const size_t db_size,
850 char *const table,
851 const size_t table_size)
852 {
853 assert(path != NULL);
854 assert(db != NULL);
855 assert(table != NULL);
856
857 // path is db + table, for example: cfsettings.table
858 const char *separator = strchr(path, '.');
859
860 if (separator == NULL)
861 {
862 Log(LOG_LEVEL_ERR, "No separator found in database path '%s'", path);
863 return false;
864 }
865
866 // Backwards compatibility with bugs - this would cause false before:
867 if ((strchr(path, '/') != NULL) || (strchr(path, '\\') != NULL))
868 {
869 Log(LOG_LEVEL_ERR,
870 "Unexpected slash in database path '%s'",
871 path);
872 return false;
873 }
874
875 assert(separator >= path);
876 const size_t db_length = separator - path;
877 if (db_length >= db_size)
878 {
879 Log(LOG_LEVEL_ERR,
880 "Database name too long (%zu bytes) in '%s'",
881 db_length,
882 path);
883 return false;
884 }
885
886 const char *table_start = separator + 1;
887 const size_t table_length = strlen(table_start);
888
889 if (table_length >= table_size)
890 {
891 Log(LOG_LEVEL_ERR,
892 "Database table name too long (%zu bytes) in '%s'",
893 table_length,
894 path);
895 return false;
896 }
897
898 memcpy(db, path, db_length);
899 memcpy(table, table_start, table_length);
900 db[db_length] = table[table_length] = '\0';
901
902 assert(strlen(db) < db_size);
903 assert(strlen(table) < table_size);
904 assert(strlen(db) == db_length);
905 assert(strlen(path) == (strlen(db) + 1 + strlen(table)));
906
907 return true;
908 }
909
910 /*****************************************************************************/
911
QueryTableColumns(char * s,char * db,char * table)912 static void QueryTableColumns(char *s, char *db, char *table)
913 {
914 snprintf(s, CF_MAXVARSIZE - 1,
915 "SELECT column_name,data_type,character_maximum_length FROM information_schema->columns WHERE table_name ='%s' AND table_schema = '%s'",
916 table, db);
917 }
918
919 /*****************************************************************************/
920
NewSQLColumns(char * table,Rlist * columns,char *** name_table,char *** type_table,int ** size_table,int ** done)921 static bool NewSQLColumns(char *table, Rlist *columns, char ***name_table, char ***type_table, int **size_table,
922 int **done)
923 {
924 int i, no_of_cols = RlistLen(columns);
925 Rlist *cols, *rp;
926
927 *name_table = (char **) xmalloc(sizeof(char *) * (no_of_cols + 1));
928 *type_table = (char **) xmalloc(sizeof(char *) * (no_of_cols + 1));
929 *size_table = (int *) xmalloc(sizeof(int) * (no_of_cols + 1));
930 *done = (int *) xmalloc(sizeof(int) * (no_of_cols + 1));
931
932 for (i = 0, rp = columns; rp != NULL; rp = rp->next, i++)
933 {
934 (*done)[i] = 0;
935
936 cols = RlistFromSplitString(RlistScalarValue(rp), ',');
937
938 if (!cols)
939 {
940 Log(LOG_LEVEL_ERR, "No columns promised for table '%s' - makes no sense", table);
941 return false;
942 }
943
944 if (cols->val.item == NULL)
945 {
946 Log(LOG_LEVEL_ERR, "Malformed column promise for table '%s' - found not even a name", table);
947 free(*name_table);
948 free(*type_table);
949 free(*size_table);
950 free(*done);
951 return false;
952 }
953
954 (*name_table)[i] = xstrdup(RlistScalarValue(cols));
955
956 if (cols->next == NULL)
957 {
958 Log(LOG_LEVEL_ERR, "Malformed column '%s' promised for table '%s' - missing a type", (*name_table)[i],
959 table);
960 free(*name_table);
961 free(*type_table);
962 free(*size_table);
963 free(*done);
964 return false;
965 }
966
967 (*type_table)[i] = xstrdup(RlistScalarValue(cols->next));
968
969 if (cols->next->next == NULL)
970 {
971 (*size_table)[i] = 0;
972 }
973 else
974 {
975 if (cols->next->next->val.item)
976 {
977 (*size_table)[i] = IntFromString(RlistScalarValue(cols->next->next));
978 }
979 else
980 {
981 (*size_table)[i] = 0;
982 }
983 }
984
985 RlistDestroy(cols);
986 }
987
988 return true;
989 }
990
991 /*****************************************************************************/
992
DeleteSQLColumns(char ** name_table,char ** type_table,int * size_table,int * done,int len)993 static void DeleteSQLColumns(char **name_table, char **type_table, int *size_table, int *done, int len)
994 {
995 int i;
996
997 if ((name_table == NULL) || (type_table == NULL) || (size_table == NULL))
998 {
999 return;
1000 }
1001
1002 for (i = 0; i < len; i++)
1003 {
1004 if (name_table[i] != NULL)
1005 {
1006 free(name_table[i]);
1007 }
1008
1009 if (type_table[i] != NULL)
1010 {
1011 free(type_table[i]);
1012 }
1013 }
1014
1015 free(name_table);
1016 free(type_table);
1017 free(size_table);
1018 free(done);
1019 }
1020
1021 /*****************************************************************************/
1022
CheckSQLDataType(char * type,char * ref_type,const Promise * pp)1023 static bool CheckSQLDataType(char *type, char *ref_type, const Promise *pp)
1024 {
1025 static char *const aliases[3][2] =
1026 {
1027 {"varchar", "character@varying"},
1028 {"varchar", "character varying"},
1029 {NULL, NULL}
1030 };
1031
1032 int i;
1033 bool ret = true;
1034
1035 for (i = 0; aliases[i][0] != NULL; i++)
1036 {
1037 if ((strcmp(ref_type, aliases[i][0]) == 0) || (strcmp(ref_type, aliases[i][1]) == 0)
1038 || (strcmp(type, aliases[i][0]) == 0) || (strcmp(type, aliases[i][1]) == 0))
1039 {
1040 if ((strcmp(type, ref_type) != 0) && (strcmp(aliases[i][0], ref_type) != 0))
1041 {
1042 Log(LOG_LEVEL_VERBOSE, "Promised column in database '%s' has a non-matching type (%s != %s)",
1043 pp->promiser, ref_type, type);
1044 ret = false;
1045 }
1046 }
1047 else
1048 {
1049 if (strcmp(type, ref_type) != 0)
1050 {
1051 Log(LOG_LEVEL_VERBOSE, "Promised column in database '%s' has a non-matching type (%s != %s)",
1052 pp->promiser, ref_type, type);
1053 ret = false;
1054 }
1055 }
1056 }
1057
1058 return ret;
1059 }
1060
1061 /*****************************************************************************/
1062
ListTables(int type,char * query)1063 static void ListTables(int type, char *query)
1064 {
1065 switch (type)
1066 {
1067 case DATABASE_TYPE_MYSQL:
1068 snprintf(query, CF_MAXVARSIZE - 1, "show tables");
1069 break;
1070
1071 case DATABASE_TYPE_POSTGRES:
1072 /* This gibberish is the simplest thing I can find in postgres */
1073
1074 snprintf(query, CF_MAXVARSIZE - 1,
1075 "SELECT c.relname as \"Name\" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_roles r ON r.oid = c.relowner LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public'");
1076 break;
1077
1078 default:
1079 snprintf(query, CF_MAXVARSIZE, "NULL QUERY");
1080 break;
1081 }
1082 }
1083