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