1 /*
2 * Copyright (C) 2019-2020 Niko Rosvall <niko@byteptr.com>
3 */
4
5 #define _XOPEN_SOURCE 700
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdbool.h>
10 #include <string.h>
11 #include <sqlite3.h>
12 #include "entry.h"
13 #include "db.h"
14 #include "utils.h"
15
16 /* sqlite callbacks */
17 static int cb_check_integrity(void *notused, int argc, char **argv, char **column_name);
18 static int cb_get_by_id(void *entry, int argc, char **argv, char **column_name);
19 static int cb_list_all(void *entry, int argc, char **argv, char **column_name);
20 static int cb_find(void *entry, int argc, char **argv, char **column_name);
21
22 /*Run integrity check for the database to detect
23 *malformed and corrupted databases. Returns true
24 *if everything is ok, false if something is wrong.
25 */
db_check_integrity(const char * path)26 static bool db_check_integrity(const char *path)
27 {
28 sqlite3 *db;
29 char *err = NULL;
30 int retval;
31 char *sql;
32
33 retval = sqlite3_open(path, &db);
34
35 if(retval)
36 {
37 fprintf(stderr, "Can't initialize: %s\n", sqlite3_errmsg(db));
38 return false;
39 }
40
41 sql = "pragma integrity_check;";
42
43 retval = sqlite3_exec(db, sql, cb_check_integrity, 0, &err);
44
45 if(retval != SQLITE_OK)
46 {
47 fprintf(stderr, "SQL error: %s\n", err);
48 sqlite3_free(err);
49 sqlite3_close(db);
50 return false;
51 }
52
53 sqlite3_close(db);
54
55 return true;
56 }
57
db_init_new(const char * path)58 bool db_init_new(const char *path)
59 {
60 sqlite3 *db;
61 char *err = NULL;
62
63 int rc = sqlite3_open(path, &db);
64
65 if(rc != SQLITE_OK)
66 {
67 fprintf(stderr, "Failed to initialize database: %s\n", sqlite3_errmsg(db));
68 sqlite3_close(db);
69
70 return false;
71 }
72
73 char *query = "create table entries"
74 "(id integer primary key, title text, user text, url text,"
75 "password text, notes text,"
76 "timestamp date default (datetime('now','localtime')));";
77
78 rc = sqlite3_exec(db, query, 0, 0, &err);
79
80 if(rc != SQLITE_OK)
81 {
82 fprintf(stderr, "Error: %s\n", err);
83 sqlite3_free(err);
84 sqlite3_close(db);
85
86 return false;
87 }
88
89 sqlite3_close(db);
90 set_file_owner_rw(path);
91
92 return true;
93 }
94
db_insert_entry(Entry_t * entry)95 bool db_insert_entry(Entry_t *entry)
96 {
97 sqlite3 *db;
98 char *err = NULL;
99 char *path = NULL;
100
101 path = read_active_database_path();
102
103 if(!path)
104 {
105 fprintf(stderr, "Error getting database path\n");
106 return false;
107 }
108
109 if(!db_check_integrity(path))
110 {
111 fprintf(stderr, "Corrupted database. Abort.\n");
112 free(path);
113 return 0;
114 }
115
116 int rc = sqlite3_open(path, &db);
117
118 if(rc != SQLITE_OK)
119 {
120 fprintf(stderr, "Failed to initialize database: %s\n", sqlite3_errmsg(db));
121 sqlite3_close(db);
122 free(path);
123
124 return false;
125 }
126
127 char *query = sqlite3_mprintf("insert into entries(title, user, url, password, notes)"
128 "values('%q','%q','%q','%q','%q')",
129 entry->title, entry->user, entry->url, entry->password,
130 entry->notes);
131
132 rc = sqlite3_exec(db, query, NULL, 0, &err);
133
134 if(rc != SQLITE_OK)
135 {
136 fprintf(stderr, "Error: %s\n", err);
137 sqlite3_free(err);
138 sqlite3_free(query);
139 sqlite3_close(db);
140 free(path);
141
142 return false;
143 }
144
145 sqlite3_free(query);
146 sqlite3_close(db);
147 free(path);
148
149 return true;
150 }
151
db_update_entry(int id,Entry_t * new_entry)152 bool db_update_entry(int id, Entry_t *new_entry)
153 {
154 sqlite3 *db;
155 char *err = NULL;
156 char *path = NULL;
157
158 path = read_active_database_path();
159
160 if(!path)
161 {
162 fprintf(stderr, "Error getting database path\n");
163 return false;
164 }
165
166 if(!db_check_integrity(path))
167 {
168 fprintf(stderr, "Corrupted database. Abort.\n");
169 free(path);
170
171 return 0;
172 }
173
174 int rc = sqlite3_open(path, &db);
175
176 if(rc != SQLITE_OK)
177 {
178 fprintf(stderr, "Failed to initialize database: %s\n", sqlite3_errmsg(db));
179 sqlite3_close(db);
180 free(path);
181
182 return false;
183 }
184
185 char *query = sqlite3_mprintf("update entries set title='%q',"
186 "user='%q',"
187 "url='%q',"
188 "password='%q',"
189 "notes='%q',timestamp=datetime('now','localtime') where id=%d;",
190 new_entry->title,
191 new_entry->user,
192 new_entry->url,
193 new_entry->password,
194 new_entry->notes,id);
195
196 rc = sqlite3_exec(db, query, NULL, 0, &err);
197
198 if(rc != SQLITE_OK)
199 {
200 fprintf(stderr, "Error: %s\n", err);
201 sqlite3_free(err);
202 sqlite3_free(query);
203 sqlite3_close(db);
204 free(path);
205
206 return false;
207 }
208
209 sqlite3_free(query);
210 sqlite3_close(db);
211 free(path);
212
213 return true;
214 }
215
216 /*Get entry which has the wanted id.
217 * Caller must free the return value.
218 */
db_get_entry_by_id(int id)219 Entry_t *db_get_entry_by_id(int id)
220 {
221 char *path = NULL;
222 sqlite3 *db;
223 int rc;
224 char *query;
225 char *err = NULL;
226 Entry_t *entry = NULL;
227
228 path = read_active_database_path();
229
230 if(!path)
231 {
232 fprintf(stderr, "Error getting database path\n");
233 return NULL;
234 }
235
236 if(!db_check_integrity(path))
237 {
238 fprintf(stderr, "Corrupted database. Abort.\n");
239 free(path);
240
241 return NULL;
242 }
243
244 rc = sqlite3_open(path, &db);
245
246 if(rc != SQLITE_OK)
247 {
248 fprintf(stderr, "Error %s\n", sqlite3_errmsg(db));
249 free(path);
250
251 return NULL;
252 }
253
254 entry = entry_new_empty();
255
256 query = sqlite3_mprintf("select id,title,user,url,password,notes,"
257 "timestamp from entries where id=%d;", id);
258
259 /* Set id to minus one by default. If query finds data
260 * we set the id back to the original one in the callback.
261 * We can uses this to easily check if we have valid data in the structure.
262 */
263 entry->id = -1;
264
265 rc = sqlite3_exec(db, query, cb_get_by_id, entry, &err);
266
267 if(rc != SQLITE_OK)
268 {
269 fprintf(stderr, "Error: %s\n", err);
270 sqlite3_free(err);
271 sqlite3_free(query);
272 free(path);
273
274 return NULL;
275 }
276
277 sqlite3_free(query);
278 sqlite3_close(db);
279 free(path);
280
281 return entry;
282 }
283
284 /* Returns true on success, false on failure.
285 * Parameter changes is set to true if entry with given
286 * id was found and deleted.
287 */
db_delete_entry(int id,bool * changes)288 bool db_delete_entry(int id, bool *changes)
289 {
290 char *path = NULL;
291 sqlite3 *db;
292 int rc;
293 char *query;
294 char *err = NULL;
295 int count;
296
297 path = read_active_database_path();
298
299 if(!path)
300 {
301 fprintf(stderr, "Error getting database path\n");
302 return false;
303 }
304
305 if(!db_check_integrity(path))
306 {
307 fprintf(stderr, "Corrupted database. Abort.\n");
308 free(path);
309
310 return false;
311 }
312
313 rc = sqlite3_open(path, &db);
314
315 if(rc != SQLITE_OK)
316 {
317 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
318 free(path);
319 return false;
320 }
321
322 query = sqlite3_mprintf("delete from entries where id=%d;", id);
323 rc = sqlite3_exec(db, query, NULL, 0, &err);
324
325 if(rc != SQLITE_OK)
326 {
327 fprintf(stderr, "Error: %s\n", err);
328 sqlite3_free(err);
329 sqlite3_free(query);
330 sqlite3_close(db);
331 free(path);
332
333 return false;
334 }
335
336 count = sqlite3_changes(db);
337
338 if(count > 0)
339 *changes = true;
340
341 sqlite3_free(query);
342 sqlite3_close(db);
343
344 free(path);
345
346 return true;
347 }
348
349 /* Get latest count of entries pointed by count_latest.
350 * -1 to get everything. -2 to get everything ordered by date
351 */
db_get_list(int count_latest)352 Entry_t *db_get_list(int count_latest)
353 {
354 char *path = NULL;
355 char *err = NULL;
356 sqlite3 *db;
357 char *query = NULL;
358
359 if(count_latest < 0 && count_latest != -1 && count_latest != -2)
360 {
361 fprintf(stderr, "Invalid parameter <count>\n");
362 return NULL;
363 }
364
365 path = read_active_database_path();
366
367 if(!path)
368 {
369 fprintf(stderr, "Error getting database path\n");
370 return NULL;
371 }
372
373 if(!db_check_integrity(path))
374 {
375 fprintf(stderr, "Corrupted database. Abort.\n");
376 free(path);
377
378 return NULL;
379 }
380
381 int rc = sqlite3_open(path, &db);
382
383 if(rc != SQLITE_OK)
384 {
385 fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
386 sqlite3_close(db);
387 free(path);
388
389 return NULL;
390 }
391
392 /* Fill our list with dummy data */
393 Entry_t *entry = entry_new("dummy", "dummy", "dummy", "dummy", "dummy");
394
395 /* Get all data or a defined count */
396 if(count_latest == -1)
397 query = "select * from entries;";
398 else if(count_latest == -2)
399 query = sqlite3_mprintf("select * from entries order by datetime(timestamp) desc");
400 else
401 query = sqlite3_mprintf("select * from entries order by datetime(timestamp) desc limit %d", count_latest);
402
403 rc = sqlite3_exec(db, query, cb_list_all, entry, &err);
404
405 if(rc != SQLITE_OK)
406 {
407 fprintf(stderr, "Error: %s\n", err);
408 sqlite3_free(err);
409
410 if(count_latest != -1 && query != NULL)
411 sqlite3_free(query);
412
413 sqlite3_close(db);
414 free(path);
415
416 return NULL;
417 }
418
419 sqlite3_close(db);
420 free(path);
421
422 return entry;
423 }
424
db_find(const char * search)425 Entry_t *db_find(const char *search)
426 {
427 char *path = NULL;
428 char *err = NULL;
429 sqlite3 *db;
430
431 path = read_active_database_path();
432
433 if(!path)
434 {
435 fprintf(stderr, "Error getting database path\n");
436 return NULL;
437 }
438
439 if(!db_check_integrity(path))
440 {
441 fprintf(stderr, "Corrupted database. Abort.\n");
442 free(path);
443
444 return NULL;
445 }
446
447 int rc = sqlite3_open(path, &db);
448
449 if(rc != SQLITE_OK)
450 {
451 fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
452 sqlite3_close(db);
453 free(path);
454
455 return NULL;
456 }
457
458 /* Fill our list with dummy data */
459 Entry_t *entry = entry_new("dummy", "dummy", "dummy", "dummy", "dummy");
460
461 /* Search the same search term from each column we're might be interested in. */
462 char *query = sqlite3_mprintf("select * from entries where title like '%%%q%%' "
463 "or user like '%%%q%%' "
464 "or url like '%%%q%%' "
465 "or notes like '%%%q%%';", search, search, search, search);
466
467 rc = sqlite3_exec(db, query, cb_find, entry, &err);
468
469 if(rc != SQLITE_OK)
470 {
471 fprintf(stderr, "Error: %s\n", err);
472 sqlite3_free(err);
473 sqlite3_free(query);
474 sqlite3_close(db);
475 free(path);
476
477 return NULL;
478 }
479
480 sqlite3_free(query);
481 sqlite3_close(db);
482 free(path);
483
484 return entry;
485 }
486
cb_check_integrity(void * notused,int argc,char ** argv,char ** column_name)487 static int cb_check_integrity(void *notused, int argc, char **argv, char **column_name)
488 {
489 for(int i = 0; i < argc; i++)
490 {
491 if(strcmp(column_name[i], "integrity_check") == 0)
492 {
493 char *result = argv[i];
494
495 if(strcmp(result, "ok") != 0)
496 return 1;
497 }
498 }
499
500 return 0;
501 }
502
cb_list_all(void * entry,int argc,char ** argv,char ** column_name)503 static int cb_list_all(void *entry, int argc, char **argv, char **column_name)
504 {
505 Entry_t *one_entry = entry_add(entry, argv[1], argv[2], argv[3], argv[4], argv[5]);
506 one_entry->id = atoi(argv[0]);
507 one_entry->stamp = strdup(argv[6]);
508
509 return 0;
510 }
511
cb_find(void * entry,int argc,char ** argv,char ** column_name)512 static int cb_find(void *entry, int argc, char **argv, char **column_name)
513 {
514 Entry_t *one_entry = entry_add(entry, argv[1], argv[2], argv[3], argv[4], argv[5]);
515 one_entry->id = atoi(argv[0]);
516 one_entry->stamp = strdup(argv[6]);
517
518 return 0;
519 }
520
cb_get_by_id(void * entry,int argc,char ** argv,char ** column_name)521 static int cb_get_by_id(void *entry, int argc, char **argv, char **column_name)
522 {
523 /*Let's not allow NULLs*/
524 if (argv[0] == NULL) {
525 return 1;
526 }
527 if (argv[1] == NULL) {
528 return 1;
529 }
530 if (argv[2] == NULL) {
531 return 1;
532 }
533 if (argv[3] == NULL) {
534 return 1;
535 }
536 if (argv[4] == NULL) {
537 return 1;
538 }
539 if (argv[5] == NULL) {
540 return 1;
541 }
542 if (argv[6] == NULL) {
543 return 1;
544 }
545
546 ((Entry_t *)entry)->id = atoi(argv[0]);
547 ((Entry_t *)entry)->title = strdup(argv[1]);
548 ((Entry_t *)entry)->user = strdup(argv[2]);
549 ((Entry_t *)entry)->url = strdup(argv[3]);
550 ((Entry_t *)entry)->password = strdup(argv[4]);
551 ((Entry_t *)entry)->notes = strdup(argv[5]);
552 ((Entry_t *)entry)->stamp = strdup(argv[6]);
553 ((Entry_t *)entry)->next = NULL;
554
555
556
557 return 0;
558 }
559