From da21dd57634aebffe0f5833b598e1128fafc0def Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Sat, 27 Apr 2024 12:31:12 +0530 Subject: npassd/collection: init --- README.md | 2 +- include/npassd/collection.h | 21 ++++ include/npassd/service.h | 4 +- include/npassd/util.h | 1 + src/npassd/collection.c | 289 ++++++++++++++++++++++++++++++++++++++++++++ src/npassd/db.c | 29 +++-- src/npassd/meson.build | 2 + src/npassd/service.c | 91 +++++++++++++- src/npassd/session.c | 8 +- src/npassd/util.c | 15 +++ 10 files changed, 444 insertions(+), 18 deletions(-) create mode 100644 include/npassd/collection.h create mode 100644 include/npassd/util.h create mode 100644 src/npassd/collection.c create mode 100644 src/npassd/util.c diff --git a/README.md b/README.md index 7ef87eb..1860026 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Todo - [ ] npassd (dbus) - [ ] org.freedesktop.Secret.Service - [x] OpenSession - - [ ] CreateCollection + - [x] CreateCollection - [ ] SearchItems - [ ] Unlock - [ ] Lock diff --git a/include/npassd/collection.h b/include/npassd/collection.h new file mode 100644 index 0000000..36d6b72 --- /dev/null +++ b/include/npassd/collection.h @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +struct collection { + sd_bus_slot *slot; + char *root, *path; + + int locked; + char *alias, *label; + uint64_t created, modified; + + LIST_ENTRY(collection) dlist; +}; + +LIST_HEAD(collection_dlist, collection); + +int collection_new(sd_bus *bus, struct sqlite3 *db, struct collection **p, + const char *alias, const char *label, const char *root); +int collection_root_make(const char *label, const char *alias, char **root); diff --git a/include/npassd/service.h b/include/npassd/service.h index b7c71af..92a833e 100644 --- a/include/npassd/service.h +++ b/include/npassd/service.h @@ -2,6 +2,7 @@ #include #include +#include "npassd/collection.h" #include "npassd/session.h" #define MAX_SESSION 128 @@ -9,9 +10,10 @@ struct service { sd_bus *bus; sd_bus_slot *slot; - struct sqlite3 *db; + struct session_dlist sessions; + struct collection_dlist collections; }; int service_init(sd_bus *bus, struct service *service); diff --git a/include/npassd/util.h b/include/npassd/util.h new file mode 100644 index 0000000..44efb9b --- /dev/null +++ b/include/npassd/util.h @@ -0,0 +1 @@ +int dbus_objpath_alnumify(char *path); diff --git a/src/npassd/collection.c b/src/npassd/collection.c new file mode 100644 index 0000000..48167bf --- /dev/null +++ b/src/npassd/collection.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include + +#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_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", "", "", 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; +} + +static void collection_free(struct collection *c) +{ + if (c == NULL) + return; + + if (c->slot != NULL) + sd_bus_slot_unref(c->slot); + if (c->path != NULL) + free(c->path); + if (c->alias != NULL) + free(c->alias); + if (c->label != NULL) + free(c->label); + if (c->root != NULL) + free(c->root); + + free(c); +} + +static int collection_db_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 (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; + 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 create 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; + } + + return 0; +} + +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_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); +} diff --git a/src/npassd/db.c b/src/npassd/db.c index 5afa45e..f55bede 100644 --- a/src/npassd/db.c +++ b/src/npassd/db.c @@ -7,17 +7,23 @@ #define DB_NAME "secret_service.sqlite" -const char sql_stmt_init[] = "CREATE TABLE IF NOT EXISTS store (" - " storeid INTEGER PRIMARY KEY," - " name TEXT NOT NULL UNIQUE" - ");" - "CREATE TABLE IF NOT EXISTS lookup (" - " key TEXT NOT NULL," - " value TEXT NOT NULL," - " storeid INTEGER," - " UNIQUE(storeid, key)," - " FOREIGN KEY(storeid) REFERENCES store(storeid)" - ");"; +const char sql_stmt_init[] = + "CREATE TABLE IF NOT EXISTS collections (" + " id INTEGER PRIMARY KEY," + " root TEXT NOT NULL UNIQUE," + " label TEXT UNIQUE," + " alias TEXT UNIQUE," + " created INTEGER," + " modified INTEGER" + ");" + "CREATE TABLE IF NOT EXISTS collection_attrs (" + " id INTEGER PRIMARY KEY," + " key TEXT NOT NULL," + " value TEXT NOT NULL," + " collectionid INTEGER," + " UNIQUE(key, collectionid)" + " FOREIGN KEY(collectionid) REFERENCES collections(id)" + ");"; int db_init(const char *store_path, struct sqlite3 **db) { @@ -41,6 +47,7 @@ int db_init(const char *store_path, struct sqlite3 **db) ret = sqlite3_exec(*db, sql_stmt_init, NULL, NULL, NULL); if (ret != SQLITE_OK) { + sqlite3_close(*db); print_err("Failed to create tables: %s", sqlite3_errmsg(*db)); return -EPERM; } diff --git a/src/npassd/meson.build b/src/npassd/meson.build index e73c28f..5f2c4ad 100644 --- a/src/npassd/meson.build +++ b/src/npassd/meson.build @@ -4,7 +4,9 @@ e = executable( 'npassd.c', 'service.c', 'session.c', + 'collection.c', 'db.c', + 'util.c', ], include_directories: npass_inc, diff --git a/src/npassd/service.c b/src/npassd/service.c index b179735..e57fefe 100644 --- a/src/npassd/service.c +++ b/src/npassd/service.c @@ -13,6 +13,8 @@ static int handle_open_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error); static int handle_search_items(sd_bus_message *msg, void *data, sd_bus_error *ret_error); +static int handle_create_collection(sd_bus_message *msg, void *data, + sd_bus_error *ret_error); static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_START(0), @@ -20,6 +22,8 @@ static const sd_bus_vtable service_vtable[] = { SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SearchItems", "a{ss}", "aoao", handle_search_items, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateCollection", "a{sv}s", "oo", + handle_create_collection, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; @@ -30,6 +34,84 @@ static int handle_search_items(__attribute__((unused)) sd_bus_message *msg, return sd_bus_reply_method_return(msg, "aoao", 0, 0); } +static int handle_create_collection(sd_bus_message *msg, void *data, + __attribute__((unused)) + sd_bus_error *ret_error) +{ + const char *label, *alias, *key; + struct service *service = data; + struct collection *collection; + char *root; + int ret; + + ret = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "{sv}"); + if (ret <= 0) { + print_err("%s", strerror(ret < 0 ? -ret : EINVAL)); + return ret < 0 ? ret : -EINVAL; + } + + ret = sd_bus_message_enter_container(msg, SD_BUS_TYPE_DICT_ENTRY, "sv"); + if (ret <= 0) { + print_err("%s", strerror(ret < 0 ? -ret : EINVAL)); + return ret < 0 ? ret : -EINVAL; + } + + ret = sd_bus_message_read(msg, "s", &key); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + ret = strcmp("org.freedesktop.Secret.Collection.Label", key); + if (ret) { + print_err("Unsupported property key: %s", key); + return -EINVAL; + } + + ret = sd_bus_message_read(msg, "v", "s", &label); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + ret = sd_bus_message_read(msg, "s", &alias); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + ret = collection_root_make(label, alias, &root); + if (ret < 0) + return ret; + + ret = collection_new(service->bus, service->db, &collection, alias, + label, root); + free(root); + if (ret < 0) + return ret; + + ret = sd_bus_reply_method_return(msg, "oo", collection->path, "/"); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + + LIST_INSERT_HEAD(&service->collections, collection, dlist); + return ret; +} + static int handle_open_session(sd_bus_message *msg, void *data, sd_bus_error *ret_error) { @@ -55,8 +137,14 @@ static int handle_open_session(sd_bus_message *msg, void *data, if (ret < 0) return ret; + ret = sd_bus_reply_method_return(msg, "vo", "s", NULL, session->path); + if (ret < 0) { + print_err("%s", strerror(-ret)); + return ret; + } + LIST_INSERT_HEAD(&service->sessions, session, dlist); - return sd_bus_reply_method_return(msg, "vo", "s", NULL, session->path); + return ret; } void service_free(struct service *service) @@ -75,6 +163,7 @@ int service_init(sd_bus *bus, struct service *service) service->bus = bus; LIST_INIT(&service->sessions); + LIST_INIT(&service->collections); ret = sd_bus_add_object_vtable(service->bus, &service->slot, DBUS_OBJECT_PATH, SERVICE_IFACE, diff --git a/src/npassd/session.c b/src/npassd/session.c index 1807ea1..52f8476 100644 --- a/src/npassd/session.c +++ b/src/npassd/session.c @@ -141,10 +141,10 @@ int session_new(sd_bus *bus, struct session **p, const char *owner) return ret; } - ret = sd_bus_match_signal(bus, &session->slot_singal, "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", "NameOwnerChanged", - handle_nameownerchanged, session); + ret = sd_bus_match_signal( + bus, &session->slot_singal, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_nameownerchanged, session); if (ret < 0) { session_free(session); print_err("%s", strerror(-ret)); diff --git a/src/npassd/util.c b/src/npassd/util.c new file mode 100644 index 0000000..9b1fdac --- /dev/null +++ b/src/npassd/util.c @@ -0,0 +1,15 @@ +#include +#include +#include + +int dbus_objpath_alnumify(char *path) +{ + for (size_t i = 0; path[i]; i++) { + if (!isalnum(path[i]) && path[i] != '/') + path[i] = '_'; + else if (i > 0 && path[i] == '/' && path[i - 1] == '/') + return -EINVAL; + } + + return 0; +} -- cgit v1.2.3