#include <errno.h>
#include <linux/limits.h>
#include <sqlite3.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include <time.h>
#include "npassd/collection.h"
#include "npassd/common.h"
#include "npassd/util.h"
#include "util.h"
#define COLLECTION_IFACE "org.freedesktop.Secret.Collection"
#define DBUS_ROOT_PREFIX "dbus"
static int collection_alloc(sd_bus *bus, struct collection **p,
const char *root, const char *alias,
const char *label, uint64_t created,
uint64_t modified);
static int collection_db_root_read(sd_bus *bus, struct sqlite3 *db,
struct collection **p, const char *alias);
static int collection_db_write(struct sqlite3 *db, struct collection *c);
static int handle_create_item(__attribute__((unused)) sd_bus_message *msg,
__attribute__((unused)) void *data,
__attribute__((unused)) sd_bus_error *ret_error);
static const sd_bus_vtable collection_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("CreateItem", "a{sv}(oayays)b", "oo", handle_create_item,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
static int handle_create_item(__attribute__((unused)) sd_bus_message *msg,
__attribute__((unused)) void *data,
__attribute__((unused)) sd_bus_error *ret_error)
{
return 0;
}
void collection_free(struct collection *c)
{
if (c == NULL)
return;
sd_bus_slot_unref(c->slot);
sd_bus_slot_unref(c->slot);
free(c->path);
free(c->alias);
free(c->label);
free(c->root);
free(c);
}
static int collection_db_root_read(sd_bus *bus, struct sqlite3 *db,
struct collection **p, const char *root)
{
const char *query, *alias, *label;
uint64_t created, modified;
sqlite3_stmt *stmt;
int ret;
query = "SELECT alias, label, created, modified "
"FROM collections "
"WHERE collections.root = ? ";
ret = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to prepare sql");
return -EPERM;
}
ret = sqlite3_bind_text(stmt, 1, root, -1, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE) {
ret = -ENOENT;
goto out_stmt_finalize;
}
if (ret != SQLITE_ROW) {
print_err("%s", "Failed to step sql");
ret = -EPERM;
goto out_stmt_finalize;
}
alias = (char *)sqlite3_column_text(stmt, 0);
label = (char *)sqlite3_column_text(stmt, 1);
created = sqlite3_column_int64(stmt, 2);
modified = sqlite3_column_int64(stmt, 3);
if (alias == NULL || label == NULL || created == 0 || modified == 0) {
print_err("%s", "Found null value in column");
ret = -EINVAL;
goto out_stmt_finalize;
}
ret = collection_alloc(bus, p, root, alias, label, created, modified);
out_stmt_finalize:
sqlite3_finalize(stmt);
return ret;
}
int collection_db_alias_read(sd_bus *bus, struct sqlite3 *db,
struct collection **p, const char *alias)
{
const char *query, *root, *label;
uint64_t created, modified;
sqlite3_stmt *stmt;
int ret;
query = "SELECT root, label, created, modified "
"FROM collections "
"WHERE collections.alias = ? ";
ret = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to prepare sql");
return -EPERM;
}
ret = sqlite3_bind_text(stmt, 1, alias, -1, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE) {
ret = -ENOENT;
goto out_stmt_finalize;
}
if (ret != SQLITE_ROW) {
print_err("%s", "Failed to step sql");
ret = -EPERM;
goto out_stmt_finalize;
}
root = (char *)sqlite3_column_text(stmt, 0);
label = (char *)sqlite3_column_text(stmt, 1);
created = sqlite3_column_int64(stmt, 2);
modified = sqlite3_column_int64(stmt, 3);
if (root == NULL || label == NULL || created == 0 || modified == 0) {
print_err("%s", "Found null value in column");
ret = -EINVAL;
goto out_stmt_finalize;
}
ret = collection_alloc(bus, p, root, alias, label, created, modified);
out_stmt_finalize:
sqlite3_finalize(stmt);
return ret;
}
static int collection_db_write(struct sqlite3 *db, struct collection *c)
{
sqlite3_stmt *stmt;
const char *query;
int ret;
query = "INSERT INTO "
"collections(root, label, alias, created, modified) "
"VALUES (?, ?, ?, ?, ?) ";
ret = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to prepare sql");
return -EPERM;
}
ret = sqlite3_bind_text(stmt, 1, c->root, -1, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_bind_text(stmt, 2, c->label, -1, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_bind_text(stmt, 3, c->alias, -1, NULL);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_bind_int64(stmt, 4, c->created);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_bind_int64(stmt, 5, c->modified);
if (ret != SQLITE_OK) {
print_err("%s", "Failed to bind sql");
ret = -EPERM;
goto out_stmt_finalize;
}
ret = sqlite3_step(stmt);
if (ret != SQLITE_DONE) {
print_err("%s", "No such alias");
ret = -EPERM;
goto out_stmt_finalize;
}
out_stmt_finalize:
sqlite3_finalize(stmt);
return ret;
}
static int collection_alloc(sd_bus *bus, struct collection **p,
const char *root, const char *alias,
const char *label, uint64_t created,
uint64_t modified)
{
struct collection *collection;
char *alias_path;
int ret;
*p = malloc(sizeof(**p));
if (*p == NULL) {
print_err("Failed to make collection: %s", strerror(errno));
return -errno;
}
collection = *p;
collection->slot = NULL;
collection->root = NULL;
collection->alias = NULL;
collection->label = NULL;
collection->locked = 0;
collection->created = created;
collection->modified = modified;
collection->root = strdup(root);
if (collection->root == NULL) {
collection_free(collection);
print_err("%s", strerror(errno));
return -errno;
}
collection->label = strdup(label);
if (collection->label == NULL) {
collection_free(collection);
print_err("%s", strerror(errno));
return -errno;
}
collection->alias = strdup(alias);
if (collection->alias == NULL) {
collection_free(collection);
print_err("%s", strerror(errno));
return -errno;
}
ret = asprintf(&collection->path, DBUS_OBJECT_PATH "/collection/%s",
collection->root);
if (ret < 0) {
collection_free(collection);
print_err("%s", "Failed to build collection object path");
return -ENOMEM;
}
ret = dbus_objpath_alnumify(collection->path);
if (ret < 0) {
collection_free(collection);
print_err("%s", strerror(-ret));
return ret;
}
ret = sd_bus_add_object_vtable(bus, &collection->slot, collection->path,
COLLECTION_IFACE, collection_vtable,
collection);
if (ret < 0) {
collection_free(collection);
print_err("Failed to connect to bus: %s", strerror(-ret));
return ret;
} else if (alias == NULL) {
return 0;
}
ret = asprintf(&alias_path, DBUS_OBJECT_PATH "/aliases/%s", alias);
if (ret < 0) {
collection_free(collection);
print_err("%s", "Failed to build collection alias path");
return -ENOMEM;
}
ret = sd_bus_add_object_vtable(bus, &collection->slot, alias_path,
COLLECTION_IFACE, collection_vtable,
collection);
free(alias_path);
if (ret < 0) {
collection_free(collection);
print_err("Failed to connect to bus: %s", strerror(-ret));
return ret;
}
return 0;
}
int collection_alias_search(struct collection_dlist *collections,
const char *alias, struct collection **collection)
{
struct collection *c;
int ret;
LIST_FOREACH (c, collections, dlist) {
ret = strcmp(c->alias, alias);
if (ret == 0) {
*collection = c;
return 0;
}
}
return -ENOENT;
}
int collection_root_make(const char *label, const char *alias, char **root)
{
int ret;
if (label == NULL && alias == NULL) {
print_err("%s", strerror(EINVAL));
return -EINVAL;
}
ret = asprintf(root, DBUS_ROOT_PREFIX " %s keyring",
alias ? alias : label);
if (ret < 0) {
print_err("%s", "Failed to build collection root path");
return -EPERM;
}
return 0;
}
int collection_new(sd_bus *bus, struct sqlite3 *db, struct collection **p,
const char *alias, const char *label, const char *root)
{
uint64_t epoch;
int ret;
ret = collection_db_root_read(bus, db, p, root);
if (ret != -ENOENT)
return ret;
errno = 0;
epoch = time(NULL);
if (errno != 0) {
print_err("Failed to get time: %s", strerror(errno));
return -errno;
}
ret = collection_alloc(bus, p, root, alias, label, epoch, epoch);
if (ret < 0)
return ret;
return collection_db_write(db, *p);
}