Make shared key store in database
This commit is contained in:
parent
74952aae1c
commit
2680bc40d6
8 changed files with 207 additions and 86 deletions
2
Makefile
2
Makefile
|
@ -25,7 +25,7 @@ all: $(SERVER) $(CLIENT)
|
||||||
|
|
||||||
$(SERVER): $(SERVERSRC) $(LIBSRC)
|
$(SERVER): $(SERVERSRC) $(LIBSRC)
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
$(CC) $(SERVERSRC) $(LIBSRC) $(INCLUDE) -o bin/$@ $(CFLAGS) $(LDFLAGS)
|
$(CC) $(SERVERSRC) $(LIBSRC) $(INCLUDE) -DUSERNAME=\"$(username)\" -o bin/$@ $(CFLAGS) $(LDFLAGS)
|
||||||
|
|
||||||
$(CLIENT): $(CLIENTSRC) $(LIBSRC)
|
$(CLIENT): $(CLIENTSRC) $(LIBSRC)
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
int sqlite_init();
|
void get_users();
|
||||||
|
uint8_t *get_sharedkey(uint8_t *username);
|
||||||
|
void save_sharedkey(uint8_t *username, uint8_t *shared_key);
|
||||||
|
void sqlite_init();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -29,5 +29,6 @@ typedef struct keypair_t {
|
||||||
|
|
||||||
keypair_t *create_keypair(char *username);
|
keypair_t *create_keypair(char *username);
|
||||||
keypair_t *get_keypair(char *username);
|
keypair_t *get_keypair(char *username);
|
||||||
|
uint8_t *get_pk_from_ks(char *username);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,7 +10,7 @@ void error(int fatal, const char *fmt, ...);
|
||||||
void *memalloc(size_t size);
|
void *memalloc(size_t size);
|
||||||
void *estrdup(void *str);
|
void *estrdup(void *str);
|
||||||
char *replace_home(char *str);
|
char *replace_home(char *str);
|
||||||
void mkdir_p(const char *destdir);
|
void mkdir_p(const char *file);
|
||||||
void write_log(int type, const char *fmt, ...);
|
void write_log(int type, const char *fmt, ...);
|
||||||
void print_bin(const unsigned char *ptr, size_t length);
|
void print_bin(const unsigned char *ptr, size_t length);
|
||||||
|
|
||||||
|
|
|
@ -32,16 +32,25 @@ keypair_t *create_keypair(char *username)
|
||||||
memcpy(pk, pk_data, PK_DATA_SIZE);
|
memcpy(pk, pk_data, PK_DATA_SIZE);
|
||||||
memcpy(pk + PK_DATA_SIZE, pk_sign, SIGN_SIZE);
|
memcpy(pk + PK_DATA_SIZE, pk_sign, SIGN_SIZE);
|
||||||
|
|
||||||
/* USE DB INSTEAD OF FILES */
|
char keyf_path[PATH_MAX];
|
||||||
char pk_path[PATH_MAX], sk_path[PATH_MAX];
|
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
||||||
sprintf(pk_path, "/home/night/%s_pk", username);
|
snprintf(keyf_path, PATH_MAX, "%s/%s/keys", data_dir, USERNAME);
|
||||||
sprintf(sk_path, "/home/night/%s_sk", username);
|
free(data_dir);
|
||||||
FILE *pkf = fopen(pk_path, "w+");
|
|
||||||
FILE *skf = fopen(sk_path, "w+");
|
if (access(keyf_path, W_OK) != 0) {
|
||||||
fwrite(pk, 1, PK_SIZE, pkf);
|
/* If data file doesn't exist, most likely data dir won't exist too */
|
||||||
fwrite(sk, 1, SK_SIZE, skf);
|
mkdir_p(keyf_path);
|
||||||
fclose(pkf);
|
}
|
||||||
fclose(skf);
|
|
||||||
|
FILE *keyf = fopen(keyf_path, "w+");
|
||||||
|
if (!keyf) {
|
||||||
|
error(1, "Error opening key file to write");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* Write key to file */
|
||||||
|
fwrite(pk, 1, PK_SIZE, keyf);
|
||||||
|
fwrite(sk, 1, SK_SIZE, keyf);
|
||||||
|
fclose(keyf);
|
||||||
|
|
||||||
keypair_t *kp = memalloc(sizeof(keypair_t));
|
keypair_t *kp = memalloc(sizeof(keypair_t));
|
||||||
memcpy(kp->pk.raw, pk_raw, PK_RAW_SIZE);
|
memcpy(kp->pk.raw, pk_raw, PK_RAW_SIZE);
|
||||||
|
@ -51,29 +60,35 @@ keypair_t *create_keypair(char *username)
|
||||||
memcpy(kp->pk.full, pk, PK_SIZE);
|
memcpy(kp->pk.full, pk, PK_SIZE);
|
||||||
|
|
||||||
memcpy(kp->sk, sk, SK_SIZE);
|
memcpy(kp->sk, sk, SK_SIZE);
|
||||||
|
write_log(LOG_INFO, "Created key pair");
|
||||||
|
|
||||||
return kp;
|
return kp;
|
||||||
}
|
}
|
||||||
|
|
||||||
keypair_t *get_keypair(char *username)
|
keypair_t *get_keypair(char *username)
|
||||||
{
|
{
|
||||||
/* REPLACE WITH DB */
|
char keyf_path[PATH_MAX];
|
||||||
char pk_path[PATH_MAX], sk_path[PATH_MAX];
|
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
||||||
sprintf(pk_path, "/home/night/%s_pk", username);
|
snprintf(keyf_path, PATH_MAX, "%s/%s/keys", data_dir, USERNAME);
|
||||||
sprintf(sk_path, "/home/night/%s_sk", username);
|
free(data_dir);
|
||||||
FILE *pkf = fopen(pk_path, "r");
|
|
||||||
FILE *skf = fopen(sk_path, "r");
|
if (access(keyf_path, W_OK) != 0) {
|
||||||
if (!pkf || !skf) {
|
/* If data file doesn't exist, most likely data dir won't exist too */
|
||||||
|
mkdir_p(keyf_path);
|
||||||
|
/* Create key pair as file doesn't exist */
|
||||||
create_keypair(username);
|
create_keypair(username);
|
||||||
printf("Error opening key files.\n");
|
}
|
||||||
|
|
||||||
|
FILE *keyf = fopen(keyf_path, "r");
|
||||||
|
if (!keyf) {
|
||||||
|
error(1, "Error opening key file to read");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pk[PK_SIZE], sk[SK_SIZE];
|
uint8_t pk[PK_SIZE], sk[SK_SIZE];
|
||||||
fread(pk, 1, PK_SIZE, pkf);
|
fread(pk, 1, PK_SIZE, keyf);
|
||||||
fread(sk, 1, SK_SIZE, skf);
|
fread(sk, 1, SK_SIZE, keyf);
|
||||||
fclose(pkf);
|
fclose(keyf);
|
||||||
fclose(skf);
|
|
||||||
|
|
||||||
keypair_t *kp = memalloc(sizeof(keypair_t));
|
keypair_t *kp = memalloc(sizeof(keypair_t));
|
||||||
|
|
||||||
|
@ -87,3 +102,21 @@ keypair_t *get_keypair(char *username)
|
||||||
|
|
||||||
return kp;
|
return kp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get public key from key server
|
||||||
|
*/
|
||||||
|
uint8_t *get_pk_from_ks(char *username)
|
||||||
|
{
|
||||||
|
size_t bin_len = PK_RAW_SIZE;
|
||||||
|
unsigned char *bin = memalloc(bin_len);
|
||||||
|
/* TEMPORARY */
|
||||||
|
if (strcmp(username, "night") == 0) {
|
||||||
|
sodium_hex2bin(bin, bin_len, "e2f0287d9c23aed8404dd8ba407e7dff8abe40fa98f0b9adf74904978a5fcd50", PK_RAW_SIZE * 2, NULL, NULL, NULL);
|
||||||
|
return bin;
|
||||||
|
} else if (strcmp(username, "palanix") == 0) {
|
||||||
|
sodium_hex2bin(bin, bin_len, "932aee08aa338108e49f65a5c4f0eb0a08a15bf717fdf8c0ff60eefd0ea014ae", PK_RAW_SIZE * 2, NULL, NULL, NULL);
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -204,14 +204,14 @@ int verify_packet(packet_t *pkt, int fd)
|
||||||
memcpy(from, pkt->data, MAX_NAME);
|
memcpy(from, pkt->data, MAX_NAME);
|
||||||
|
|
||||||
/* TODO: replace with db operations */
|
/* TODO: replace with db operations */
|
||||||
keypair_t *kp_from = get_keypair(from);
|
uint8_t *pk = get_pk_from_ks(from);
|
||||||
|
|
||||||
/* Verify data confidentiality by signature */
|
/* Verify data confidentiality by signature */
|
||||||
/* Verify data integrity by hash */
|
/* Verify data integrity by hash */
|
||||||
uint8_t hash[HASH_SIZE];
|
uint8_t hash[HASH_SIZE];
|
||||||
crypto_generichash(hash, HASH_SIZE, pkt->data, pkt->length, NULL, 0);
|
crypto_generichash(hash, HASH_SIZE, pkt->data, pkt->length, NULL, 0);
|
||||||
|
|
||||||
if (crypto_sign_verify_detached(pkt->signature, hash, HASH_SIZE, kp_from->pk.raw) != 0) {
|
if (crypto_sign_verify_detached(pkt->signature, hash, HASH_SIZE, pk) != 0) {
|
||||||
/* Not match */
|
/* Not match */
|
||||||
error(0, "Cannot verify data integrity");
|
error(0, "Cannot verify data integrity");
|
||||||
packet_t *error_pkt = create_packet(ZSM_STA_ERROR_INTEGRITY,
|
packet_t *error_pkt = create_packet(ZSM_STA_ERROR_INTEGRITY,
|
||||||
|
|
|
@ -76,28 +76,38 @@ char *replace_home(char *str)
|
||||||
/*
|
/*
|
||||||
* Recursively create directory by creating each subdirectory
|
* Recursively create directory by creating each subdirectory
|
||||||
* like mkdir -p
|
* like mkdir -p
|
||||||
|
* Create the parent folder(s) of given file
|
||||||
*/
|
*/
|
||||||
void mkdir_p(const char *destdir)
|
void mkdir_p(const char *file)
|
||||||
{
|
{
|
||||||
char *path = memalloc(PATH_MAX);
|
char *path = memalloc(PATH_MAX);
|
||||||
char dir_path[PATH_MAX];
|
char dir_path[PATH_MAX];
|
||||||
|
dir_path[0] = '\0';
|
||||||
|
|
||||||
if (destdir[0] == '~') {
|
if (file[0] == '~') {
|
||||||
char *home = getenv("HOME");
|
char *home = getenv("HOME");
|
||||||
if (home == NULL) {
|
if (home == NULL) {
|
||||||
write_log(LOG_ERROR, "$HOME not defined");
|
write_log(LOG_ERROR, "$HOME not defined");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* replace ~ with home */
|
/* replace ~ with home */
|
||||||
snprintf(path, PATH_MAX, "%s%s", home, destdir + 1);
|
snprintf(path, PATH_MAX, "%s%s", home, file + 1);
|
||||||
} else {
|
} else {
|
||||||
strcpy(path, destdir);
|
strcpy(path, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix first / not appearing in the string */
|
/* fix first / not appearing in the string */
|
||||||
if (path[0] == '/')
|
if (path[0] == '/')
|
||||||
strcat(dir_path, "/");
|
strcat(dir_path, "/");
|
||||||
|
|
||||||
|
/* Find last occurrence of '/' */
|
||||||
|
char *last_slash = strrchr(path, '/');
|
||||||
|
|
||||||
|
/* Remove last slash */
|
||||||
|
if (last_slash != NULL) {
|
||||||
|
path[last_slash - path] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
char *token = strtok(path, "/");
|
char *token = strtok(path, "/");
|
||||||
while (token != NULL) {
|
while (token != NULL) {
|
||||||
strcat(dir_path, token);
|
strcat(dir_path, token);
|
||||||
|
@ -127,13 +137,13 @@ void write_log(int type, const char *fmt, ...)
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
char *client_log = memalloc(PATH_MAX);
|
char client_log[PATH_MAX];
|
||||||
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
||||||
snprintf(client_log, PATH_MAX, "%s/%s", data_dir, "zen.log");
|
snprintf(client_log, PATH_MAX, "%s/%s/zen.log", data_dir, USERNAME);
|
||||||
free(data_dir);
|
free(data_dir);
|
||||||
if (access(client_log, W_OK) != 0) {
|
if (access(client_log, W_OK) != 0) {
|
||||||
/* If log file doesn't exist, most likely data dir won't exist too */
|
/* If log file doesn't exist, most likely data dir won't exist too */
|
||||||
mkdir_p(CLIENT_DATA_DIR);
|
mkdir_p(client_log);
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *log = fopen(client_log, "a");
|
FILE *log = fopen(client_log, "a");
|
||||||
|
@ -148,8 +158,9 @@ void write_log(int type, const char *fmt, ...)
|
||||||
strftime(time, 22, "%Y-%m-%d %H:%M:%S ", t);
|
strftime(time, 22, "%Y-%m-%d %H:%M:%S ", t);
|
||||||
char details[2 + type_len + 22];
|
char details[2 + type_len + 22];
|
||||||
snprintf(details, 2 + type_len + 22, "%s%s", logtype, time);
|
snprintf(details, 2 + type_len + 22, "%s%s", logtype, time);
|
||||||
fprintf(log, "%s\n", details);
|
fprintf(log, "%s", details);
|
||||||
vfprintf(log, fmt, args);
|
vfprintf(log, fmt, args);
|
||||||
|
fprintf(log, "\n");
|
||||||
fclose(log);
|
fclose(log);
|
||||||
}
|
}
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
161
src/zen/db.c
161
src/zen/db.c
|
@ -5,68 +5,141 @@
|
||||||
#include "client/db.h"
|
#include "client/db.h"
|
||||||
#include "client/user.h"
|
#include "client/user.h"
|
||||||
|
|
||||||
static int callback(void *ignore, int argc, char **argv, char **azColName)
|
sqlite3 *db;
|
||||||
|
char zen_db_path[PATH_MAX];
|
||||||
|
|
||||||
|
static int get_users_callback(void *_, int argc, char **argv, char **col_name)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
for(int i = 0; i < argc; i++) {
|
||||||
|
printf("%s = %s\n", column[i], argv[i] ? argv[i] : "NULL");
|
||||||
|
}
|
||||||
|
*/
|
||||||
add_username(argv[0]);
|
add_username(argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_shared_key(void *ignore, int argc, char **argv, char **column)
|
void get_users()
|
||||||
{
|
{
|
||||||
for(int i = 0; i < argc; i++) {
|
char *err_msg;
|
||||||
printf("%s = %s\n", column[i], argv[i] ? argv[i] : "NULL");
|
sqlite3_stmt *statement;
|
||||||
|
|
||||||
|
if (sqlite3_open(zen_db_path, &db) != SQLITE_OK) {
|
||||||
|
error(0, "Cannot open database: %s", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
}
|
}
|
||||||
printf("\n");
|
|
||||||
return 0;
|
char *sql = "SELECT * FROM Keys;";
|
||||||
|
|
||||||
|
if (sqlite3_exec(db, sql, get_users_callback, NULL, &err_msg)
|
||||||
|
!= SQLITE_OK) {
|
||||||
|
error(0, "Failed to exec statement: %s", err_msg);
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_close(db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Get shared key betweeen username
|
||||||
|
*/
|
||||||
|
uint8_t *get_sharedkey(uint8_t *username)
|
||||||
|
{
|
||||||
|
sqlite3_stmt *statement;
|
||||||
|
uint8_t *shared_key = memalloc(SHARED_KEY_SIZE);
|
||||||
|
/* Make all bytes be 0 */
|
||||||
|
memset(shared_key, 0, SHARED_KEY_SIZE);
|
||||||
|
|
||||||
|
if (sqlite3_open(zen_db_path, &db) != SQLITE_OK) {
|
||||||
|
error(0, "Cannot open database: %s", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sql = "SELECT SharedKey FROM Keys WHERE Username = ?;";
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) != SQLITE_OK) {
|
||||||
|
error(0, "Failed to prepare statement: %s", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(statement, 1, username, strlen(username), SQLITE_STATIC);
|
||||||
|
|
||||||
|
if (sqlite3_step(statement) == SQLITE_ROW) {
|
||||||
|
const void *blob = sqlite3_column_blob(statement, 0);
|
||||||
|
memcpy(shared_key, blob, SHARED_KEY_SIZE);
|
||||||
|
} else {
|
||||||
|
free(shared_key);
|
||||||
|
/* Set it to NULL so it can be returned */
|
||||||
|
shared_key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
sqlite3_close(db);
|
||||||
|
return shared_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
int sqlite_init()
|
* Saved shared key with username to database
|
||||||
|
*/
|
||||||
|
void save_sharedkey(uint8_t *username, uint8_t *shared_key)
|
||||||
{
|
{
|
||||||
sqlite3 *db;
|
if (sqlite3_open(zen_db_path, &db) != SQLITE_OK) {
|
||||||
char *err_msg = 0;
|
error(0, "Cannot open database: %s", sqlite3_errmsg(db));
|
||||||
|
|
||||||
char *zen_db = memalloc(PATH_MAX);
|
|
||||||
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
|
||||||
snprintf(zen_db, PATH_MAX, "%s/%s", data_dir, "data.db");
|
|
||||||
free(data_dir);
|
|
||||||
if (access(zen_db, W_OK) != 0) {
|
|
||||||
/* If data file doesn't exist, most likely data dir won't exist too */
|
|
||||||
mkdir_p(CLIENT_DATA_DIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
int rc = sqlite3_open(zen_db, &db);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
|
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *users_statement = "CREATE TABLE IF NOT EXISTS Users(Username TEXT, SharedKey TEXT, Test TEXT);";
|
sqlite3_stmt *statement;
|
||||||
char *shared_key_statement = "SELECT * FROM Users;";
|
|
||||||
char *messages_statement = "CREATE TABLE IF NOT EXISTS Messages(Username TEXT, );";
|
|
||||||
//"INSERT INTO Users VALUES('night', 'test', '1');";
|
|
||||||
|
|
||||||
rc = sqlite3_exec(db, users_statement, 0, 0, &err_msg);
|
/* Statement to execute with values to be replaced */
|
||||||
|
char *sql = "INSERT OR REPLACE INTO Keys (Username,SharedKey)"
|
||||||
|
"VALUES (?,?);";
|
||||||
|
printf("in db\n");
|
||||||
|
print_bin(shared_key, SHARED_KEY_SIZE);
|
||||||
|
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) != SQLITE_OK) {
|
||||||
|
error(0, "Failed to prepare statement: %s", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
sqlite3_bind_text(statement, 1, username, strlen(username), SQLITE_STATIC);
|
||||||
|
sqlite3_bind_blob(statement, 2, shared_key, SHARED_KEY_SIZE, SQLITE_STATIC);
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
if (sqlite3_step(statement) != SQLITE_DONE) {
|
||||||
error(0, "SQL error: %s", err_msg);
|
error(0, "Failed to execute statement");
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
sqlite3_close(db);
|
||||||
|
write_log(LOG_INFO, "Saved shared key with %s to database", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sqlite_init()
|
||||||
|
{
|
||||||
|
char *err_msg;
|
||||||
|
|
||||||
|
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
||||||
|
snprintf(zen_db_path, PATH_MAX, "%s/%s/data.db", data_dir, USERNAME);
|
||||||
|
free(data_dir);
|
||||||
|
if (access(zen_db_path, W_OK) != 0) {
|
||||||
|
/* If data file doesn't exist, most likely data dir won't exist too */
|
||||||
|
mkdir_p(zen_db_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sqlite3_open(zen_db_path, &db) != SQLITE_OK) {
|
||||||
|
deinit();
|
||||||
|
error(1, "Cannot open database: %s", sqlite3_errmsg(db));
|
||||||
|
sqlite3_close(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create Keys Table if it is doesn't exist with Username being id */
|
||||||
|
char *create_keys_table = "CREATE TABLE IF NOT EXISTS Keys("
|
||||||
|
"Username TEXT NOT NULL, SharedKey BLOB NOT NULL,"
|
||||||
|
"PRIMARY KEY(Username));";
|
||||||
|
|
||||||
|
if (sqlite3_exec(db, create_keys_table, 0, 0, &err_msg) != SQLITE_OK) {
|
||||||
|
error(0, "Cannot create Keys table: %s", err_msg);
|
||||||
sqlite3_free(err_msg);
|
sqlite3_free(err_msg);
|
||||||
} else {
|
} else {
|
||||||
/* error(0, "Table created successfully"); */
|
write_log(LOG_INFO, "Keys Table created successfully");
|
||||||
}
|
|
||||||
|
|
||||||
// Select and print all entries
|
|
||||||
rc = sqlite3_exec(db, "SELECT * FROM Users", callback, NULL, &err_msg);
|
|
||||||
|
|
||||||
if (rc != SQLITE_OK) {
|
|
||||||
error(0, "SQL error: %s\n", err_msg);
|
|
||||||
sqlite3_free(err_msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue