From c238619b75bb403e344e50f1aaeb7909ecc2d9d2 Mon Sep 17 00:00:00 2001 From: Thorsten Kukuk Date: Fri, 13 Dec 2024 17:10:50 +0100 Subject: [PATCH] Add daemon using varlink for communication --- include/wtmpdb.h | 10 +- lib/basics.h | 17 + lib/libwtmpdb.c | 126 +++++- lib/libwtmpdb.map | 5 +- lib/logwtmpdb.c | 8 +- lib/mkdir_p.c | 77 ++++ lib/mkdir_p.h | 33 ++ lib/sqlite.c | 92 ++-- lib/sqlite.h | 2 +- lib/varlink.c | 491 +++++++++++++++++++++ lib/varlink.h | 43 ++ meson.build | 17 +- src/varlink-org.openSUSE.wtmpdb.c | 93 ++++ src/varlink-org.openSUSE.wtmpdb.h | 7 + src/wtmpdb.c | 2 +- src/wtmpdbd.c | 701 ++++++++++++++++++++++++++++++ tests/meson.build | 11 + tests/tst-get_id.c | 58 +++ tests/tst-varlink.c | 152 +++++++ 19 files changed, 1864 insertions(+), 81 deletions(-) create mode 100644 lib/basics.h create mode 100644 lib/mkdir_p.c create mode 100644 lib/mkdir_p.h create mode 100644 lib/varlink.c create mode 100644 lib/varlink.h create mode 100644 src/varlink-org.openSUSE.wtmpdb.c create mode 100644 src/varlink-org.openSUSE.wtmpdb.h create mode 100644 src/wtmpdbd.c create mode 100644 tests/tst-get_id.c create mode 100644 tests/tst-varlink.c diff --git a/include/wtmpdb.h b/include/wtmpdb.h index d26dd75..63592fa 100644 --- a/include/wtmpdb.h +++ b/include/wtmpdb.h @@ -32,6 +32,10 @@ #define _PATH_WTMPDB "/var/lib/wtmpdb/wtmp.db" +#define _VARLINK_WTMPDB_SOCKET_DIR "/run/wtmpdb" +#define _VARLINK_WTMPDB_SOCKET_WRITER _VARLINK_WTMPDB_SOCKET_DIR"/writer.socket" +#define _VARLINK_WTMPDB_SOCKET_READER _VARLINK_WTMPDB_SOCKET_DIR"/reader.socket" + #define EMPTY 0 /* No valid user accounting information. */ #define BOOT_TIME 1 /* Time of system boot. */ #define RUNLEVEL 2 /* The system's runlevel. Unused with systemd. */ @@ -54,7 +58,11 @@ extern int wtmpdb_read_all (const char *db_path, int (*cb_func) (void *unused, int argc, char **argv, char **azColName), char **error); -extern int wtmpdb_rotate (const char *db_path, const int days, char **error, +extern int wtmpdb_read_all_v2 (const char *db_path, + int (*cb_func) (void *unused, int argc, + char **argv, char **azColName), + void *userdata, char **error); +extern int wtmpdb_rotate (const char *db_path, const int days, char **error, char **wtmpdb_name, uint64_t *entries); /* Returns last "BOOT_TIME" entry as usec */ diff --git a/lib/basics.h b/lib/basics.h new file mode 100644 index 0000000..2a51e29 --- /dev/null +++ b/lib/basics.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#define _cleanup_(x) __attribute__((__cleanup__(x))) +#define _unused_(x) x __attribute__((unused)) + +#define mfree(memory) \ + ({ \ + free(memory); \ + (typeof(memory)) NULL; \ + }) + +static inline void freep(void *p) { + *(void**)p = mfree(*(void**) p); +} + diff --git a/lib/libwtmpdb.c b/lib/libwtmpdb.c index 8b05466..d8fa1b6 100644 --- a/lib/libwtmpdb.c +++ b/lib/libwtmpdb.c @@ -25,10 +25,16 @@ POSSIBILITY OF SUCH DAMAGE. */ +#include #include +#include +#include "basics.h" #include "wtmpdb.h" #include "sqlite.h" +#include "varlink.h" + +static int varlink_is_active = 1; /* Add new wtmp entry to db. @@ -40,8 +46,27 @@ wtmpdb_login (const char *db_path, int type, const char *user, uint64_t usec_login, const char *tty, const char *rhost, const char *service, char **error) { - return sqlite_login (db_path?db_path:_PATH_WTMPDB, type, user, usec_login, tty, rhost, - service, error); + /* we can use varlink only if no specific database is requested */ + if (varlink_is_active && db_path == NULL) + { + int64_t id; + + id = varlink_login (type, user, usec_login, tty, rhost, + service, error); + if (id >= 0) + return id; + + if (id == -ECONNREFUSED) + { + varlink_is_active = 0; + *error = mfree (*error); + } + else + return id; /* return the error if wtmpdbd is active */ + } + + return sqlite_login (db_path?db_path:_PATH_WTMPDB, type, user, + usec_login, tty, rhost, service, error); } /* @@ -54,12 +79,48 @@ int wtmpdb_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error) { + /* we can use varlink only if no specific database is requested */ + if (varlink_is_active && db_path == NULL) + { + int r; + + r = varlink_logout (id, usec_logout, error); + if (r >= 0) + return r; + + if (id == -ECONNREFUSED) + { + varlink_is_active = 0; + *error = mfree (*error); + } + else + return r; /* return the error if wtmpdbd is active */ + } + return sqlite_logout (db_path?db_path:_PATH_WTMPDB, id, usec_logout, error); } int64_t wtmpdb_get_id (const char *db_path, const char *tty, char **error) { + /* we can use varlink only if no specific database is requested */ + if (varlink_is_active && db_path == NULL) + { + int64_t id; + + id = varlink_get_id (tty, error); + if (id >= 0) + return id; + + if (id == -ECONNREFUSED) + { + varlink_is_active = 0; + *error = mfree (*error); + } + else + return id; /* return the error if wtmpdbd is active */ + } + return sqlite_get_id (db_path?db_path:_PATH_WTMPDB, tty, error); } @@ -67,27 +128,74 @@ wtmpdb_get_id (const char *db_path, const char *tty, char **error) each entry. Returns 0 on success, -1 on failure. */ int -wtmpdb_read_all (const char *db_path, - int (*cb_func)(void *unused, int argc, char **argv, - char **azColName), - char **error) +wtmpdb_read_all (const char *db_path, + int (*cb_func)(void *unused, int argc, char **argv, + char **azColName), + char **error) { - return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, cb_func, error); + return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, cb_func, NULL, error); +} + +int +wtmpdb_read_all_v2 (const char *db_path, + int (*cb_func)(void *unused, int argc, char **argv, + char **azColName), + void *userdata, char **error) +{ + return sqlite_read_all (db_path?db_path:_PATH_WTMPDB, cb_func, userdata, error); } /* Reads all entries from database and calls the callback function for each entry. - Returns 0 on success, -1 on failure. */ + Returns 0 on success, < 0 on failure. */ int wtmpdb_rotate (const char *db_path, const int days, char **error, char **wtmpdb_name, uint64_t *entries) { - return sqlite_rotate (db_path, days, wtmpdb_name, entries, error); + /* we can use varlink only if no specific database is requested */ + if (varlink_is_active && db_path == NULL) + { + int r; + + r = varlink_rotate (days, wtmpdb_name, entries, error); + if (r >= 0) + return r; + + if (r == -ECONNREFUSED) + { + varlink_is_active = 0; + *error = mfree (*error); + } + else + return r; /* return the error if wtmpdbd is active */ + } + + return sqlite_rotate (db_path?db_path:_PATH_WTMPDB, days, wtmpdb_name, entries, error); } +/* returns boottime entry on success or 0 in error case */ uint64_t wtmpdb_get_boottime (const char *db_path, char **error) { + /* we can use varlink only if no specific database is requested */ + if (varlink_is_active && db_path == NULL) + { + int r; + uint64_t boottime; + + r = varlink_get_boottime (&boottime, error); + if (r >= 0) + return boottime; + + if (r == -ECONNREFUSED) + { + varlink_is_active = 0; + *error = mfree (*error); + } + else + return 0; /* return the error if wtmpdbd is active */ + } + return sqlite_get_boottime (db_path?db_path:_PATH_WTMPDB, error); } diff --git a/lib/libwtmpdb.map b/lib/libwtmpdb.map index e41a5b3..18d1139 100644 --- a/lib/libwtmpdb.map +++ b/lib/libwtmpdb.map @@ -16,4 +16,7 @@ LIBWTMPDB_0.8 { global: wtmpdb_get_boottime; } LIBWTMPDB_0.7; - +LIBWTMPDB_0.50 { + global: + wtmpdb_read_all_v2; +} LIBWTMPDB_0.8; diff --git a/lib/logwtmpdb.c b/lib/logwtmpdb.c index 7e1807a..d767303 100644 --- a/lib/logwtmpdb.c +++ b/lib/logwtmpdb.c @@ -64,13 +64,13 @@ logwtmpdb (const char *db_path, const char *tty, const char *name, if (name != NULL && strlen (name) > 0) { /* login */ - retval = wtmpdb_login (db_path ? db_path : _PATH_WTMPDB, USER_PROCESS, - name, time, tty, host, service, error); + retval = wtmpdb_login (db_path, USER_PROCESS, name, time, tty, + host, service, error); } else { /* logout */ - int64_t id = wtmpdb_get_id (db_path ? db_path : _PATH_WTMPDB, tty, error); - retval = wtmpdb_logout (db_path ? db_path : _PATH_WTMPDB, id, time, error); + int64_t id = wtmpdb_get_id (db_path, tty, error); + retval = wtmpdb_logout (db_path, id, time, error); } return retval; diff --git a/lib/mkdir_p.c b/lib/mkdir_p.c new file mode 100644 index 0000000..a390022 --- /dev/null +++ b/lib/mkdir_p.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2024, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include + +#include "mkdir_p.h" + +int +mkdir_p(const char *path, mode_t mode) +{ + if (path == NULL) + return -EINVAL; + + if (mkdir(path, mode) == 0) + return 0; + + if (errno == EEXIST) + { + struct stat st; + + /* Check if the existing path is a directory */ + if (stat(path, &st) != 0) + return -errno; + + /* If not, fail with ENOTDIR */ + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + /* if it is a directory, return */ + return 0; + } + + /* If it fails for any reason but ENOENT, fail */ + if (errno != ENOENT) + return -errno; + + char *buf = strdup(path); + if (buf == NULL) + return -ENOMEM; + + int r = mkdir_p(dirname(buf), mode); + free(buf); + /* if we couldn't create the parent, fail, too */ + if (r < 0) + return r; + + return mkdir(path, mode); +} + diff --git a/lib/mkdir_p.h b/lib/mkdir_p.h new file mode 100644 index 0000000..db2d670 --- /dev/null +++ b/lib/mkdir_p.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2024, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +extern int mkdir_p(const char *path, mode_t mode); + diff --git a/lib/sqlite.c b/lib/sqlite.c index 43b3be9..3871ce2 100644 --- a/lib/sqlite.c +++ b/lib/sqlite.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: BSD-2-Clause - Copyright (c) 2023, Thorsten Kukuk + Copyright (c) 2023, 2024 Thorsten Kukuk Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -38,53 +38,10 @@ #include "wtmpdb.h" #include "sqlite.h" +#include "mkdir_p.h" #define TIMEOUT 5000 /* 5 sec */ -/* Begin - local helper functions */ - -static int -mkdir_p(const char *path, mode_t mode) -{ - if (path == NULL) - return -EINVAL; - - if (mkdir(path, mode) == 0) - return 0; - - if (errno == EEXIST) - { - struct stat st; - - /* Check if the existing path is a directory */ - if (stat(path, &st) != 0) - return -errno; - - /* If not, fail with ENOTDIR */ - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; - - /* if it is a directory, return */ - return 0; - } - - /* If it fails for any reason but ENOENT, fail */ - if (errno != ENOENT) - return -errno; - - char *buf = strdup(path); - if (buf == NULL) - return -ENOMEM; - - int r = mkdir_p(dirname(buf), mode); - free(buf); - /* if we couldn't create the parent, fail, too */ - if (r < 0) - return r; - - return mkdir(path, mode); -} - static void strip_extension(char *in_str) { @@ -102,9 +59,6 @@ strip_extension(char *in_str) } } -/* End - local helper functions */ - - static sqlite3 * open_database_ro (const char *path, char **error) { @@ -392,37 +346,55 @@ search_id (sqlite3 *db, const char *tty, char **error) if (sqlite3_prepare_v2 (db, sql, -1, &res, 0) != SQLITE_OK) { + int r = -ENOTSUP; if (error) if (asprintf (error, "search_id: Failed to execute statement: %s", sqlite3_errmsg (db)) < 0) - *error = strdup ("search_id: Out of memory"); - - return -1; + { + r = -ENOMEM; + *error = strdup ("search_id: Out of memory"); + } + return r; } if (sqlite3_bind_text (res, 1, tty, -1, SQLITE_STATIC) != SQLITE_OK) { + int r = -EPROTO; if (error) if (asprintf (error, "search_id: Failed to create search query: %s", sqlite3_errmsg (db)) < 0) - *error = strdup("search_id: Out of memory"); + { + r = -ENOMEM; + *error = strdup("search_id: Out of memory"); + } sqlite3_finalize(res); - return -1; + return r; } int step = sqlite3_step (res); if (step == SQLITE_ROW) id = sqlite3_column_int64 (res, 0); + else if (step == SQLITE_DONE) + { + id = -ENOENT; + if (error) + if (asprintf (error, "search_id: Open entry for tty '%s' not found", tty) < 0) + { + *error = strdup("search_id: Out of memory"); + id = -ENOMEM; + } + } else { + id = -ENOENT; if (error) - if (asprintf (error, "search_id: TTY '%s' without logout time not found (%d)", tty, step) < 0) - *error = strdup("search_id: Out of memory"); - - sqlite3_finalize (res); - return -1; + if (asprintf (error, "search_id: sqlite3_step returned: %d", step) < 0) + { + *error = strdup("search_id: Out of memory"); + id = -ENOMEM; + } } sqlite3_finalize (res); @@ -453,7 +425,7 @@ int sqlite_read_all (const char *db_path, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), - char **error) + void *userdata, char **error) { sqlite3 *db; char *err_msg = 0; @@ -463,7 +435,7 @@ sqlite_read_all (const char *db_path, char *sql = "SELECT * FROM wtmp ORDER BY Login DESC, Logout ASC"; - if (sqlite3_exec (db, sql, cb_func, NULL, &err_msg) != SQLITE_OK) + if (sqlite3_exec (db, sql, cb_func, userdata, &err_msg) != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_read_all: SQL error: %s", err_msg) < 0) diff --git a/lib/sqlite.h b/lib/sqlite.h index ce5dacd..2cfcfdf 100644 --- a/lib/sqlite.h +++ b/lib/sqlite.h @@ -40,7 +40,7 @@ extern int64_t sqlite_get_id (const char *db_path, const char *tty, extern int sqlite_read_all (const char *db_path, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), - char **error); + void *userdata, char **error); extern uint64_t sqlite_get_boottime (const char *db_path, char **error); extern int sqlite_rotate (const char *db_path, const int days, char **wtmpdb_name, uint64_t *entries, diff --git a/lib/varlink.c b/lib/varlink.c new file mode 100644 index 0000000..448f18f --- /dev/null +++ b/lib/varlink.c @@ -0,0 +1,491 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2024, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "config.h" + +#if HAVE_SYSTEMD + +#include +#include +#include + +#include "basics.h" +#include "varlink.h" +#include "wtmpdb.h" + +/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time + * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ +#define TAKE_GENERIC(var, type, nullvalue) \ + ({ \ + type *_pvar_ = &(var); \ + type _var_ = *_pvar_; \ + type _nullvalue_ = nullvalue; \ + *_pvar_ = _nullvalue_; \ + _var_; \ + }) +#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) +#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) + +static int +connect_to_wtmpdbd(sd_varlink **ret, const char *socket, char **error) +{ + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + int r; + + r = sd_varlink_connect_address(&link, socket); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to connect to %s: %s", + socket, strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + *ret = TAKE_PTR(link); + return 0; +} + +struct id_error { + int64_t id; + char *error; +}; + +static void +id_error_free (struct id_error *var) +{ + var->error = mfree(var->error); +} + +/* + Add new wtmp entry to db via varlink + login timestamp is in usec. + Returns ID (>=0) on success, < 0 on failure. + */ +int64_t +varlink_login (int type, const char *user, uint64_t usec_login, + const char *tty, const char *rhost, + const char *service, char **error) +{ + _cleanup_(id_error_free) struct id_error p = { + .id = -1, + .error = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "ID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct id_error, id), 0 }, + { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct id_error, error), 0 }, + {} + }; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + sd_json_variant *result; + int r; + + r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET_WRITER, error); + if (r < 0) + return r; + + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR("Type", SD_JSON_BUILD_INTEGER(type)), + SD_JSON_BUILD_PAIR("User", SD_JSON_BUILD_STRING(user)), + SD_JSON_BUILD_PAIR("LoginTime", SD_JSON_BUILD_INTEGER(usec_login))); + if (r >= 0 && tty) + r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR("TTY", SD_JSON_BUILD_STRING(tty))); + if (r >= 0 && rhost) + r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR("RemoteHost", SD_JSON_BUILD_STRING(rhost))); + if (r >= 0 && service) + r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR("Service", SD_JSON_BUILD_STRING(service))); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to build JSON data: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + const char *error_id; + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.Login", params, &result, &error_id); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to call Login method: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + /* dispatch before checking error_id, we may need the result for the error + message */ + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to parse JSON answer: %s", + strerror(-r)) < 0) + *error = strdup("Out of memory"); + return r; + } + + if (error_id && strlen(error_id) > 0) + { + if (error) + { + if (p.error) + *error = strdup(p.error); + else + *error = strdup(error_id); + } + return -EIO; + } + + return p.id; +} + +struct status { + bool success; + char *error; +}; + +static void +status_free (struct status *var) +{ + var->error = mfree(var->error); +} + +int +varlink_logout (int64_t id, uint64_t usec_logout, char **error) +{ + _cleanup_(status_free) struct status p = { + .success = false, + .error = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct status, success), 0 }, + { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct status, error), 0 }, + {} + }; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + sd_json_variant *result; + int r; + + r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET_WRITER, error); + if (r < 0) + return r; + + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR("ID", SD_JSON_BUILD_INTEGER(id)), + SD_JSON_BUILD_PAIR("LogoutTime", SD_JSON_BUILD_INTEGER(usec_logout))); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to build JSON data: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + const char *error_id; + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.Logout", params, &result, &error_id); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to call Logout method: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + /* dispatch before checking error_id, we may need the result for the error + message */ + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to parse JSON answer: %s", + strerror(-r)) < 0) + *error = strdup("Out of memory"); + return r; + } + + if (error_id && strlen(error_id) > 0) + { + if (error) + { + if (p.error) + *error = strdup(p.error); + else + *error = strdup(error_id); + } + return -EIO; + } + + return 0; +} + + +int64_t +varlink_get_id (const char *tty, char **error) +{ + _cleanup_(id_error_free) struct id_error p = { + .id = -1, + .error = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "ID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct id_error, id), 0 }, + { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct id_error, error), 0 }, + {} + }; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + sd_json_variant *result; + const char *error_id; + int r; + + r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET_READER, error); + if (r < 0) + return r; + + r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("TTY", SD_JSON_BUILD_STRING(tty))); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to build JSON data: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.GetID", params, &result, &error_id); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to call GetID method: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + /* dispatch before checking error_id, we may need the result for the error + message */ + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to parse JSON answer: %s", + strerror(-r)) < 0) + *error = strdup("Out of memory"); + return r; + } + + if (error_id && strlen(error_id) > 0) + { + if (error) + { + if (p.error) + *error = strdup(p.error); + else + *error = strdup(error_id); + } + if (strcmp(error_id, "org.openSUSE.rebootmgr.NoEntryFound") == 0) + return -ENOENT; + else + return -EIO; + } + + return p.id; +} + +struct boottime { + bool success; + uint64_t boottime; + char *error; +}; + +static void +boottime_free (struct boottime *var) +{ + var->error = mfree(var->error); +} + +int +varlink_get_boottime (uint64_t *boottime, char **error) +{ + _cleanup_(boottime_free) struct boottime p = { + .success = false, + .boottime = -1, + .error = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct boottime, success), 0 }, + { "BootTime", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct boottime, boottime), 0 }, + { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct boottime, error), 0 }, + {} + }; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + sd_json_variant *result; + const char *error_id; + int r; + + r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET_READER, error); + if (r < 0) + return r; + + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.GetBootTime", NULL, &result, &error_id); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to call BootTime method: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + /* dispatch before checking error_id, we may need the result for the error + message */ + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to parse JSON answer: %s", + strerror(-r)) < 0) + *error = strdup("Out of memory"); + return r; + } + + if (error_id && strlen(error_id) > 0) + { + if (error) + { + if (p.error) + *error = strdup(p.error); + else + *error = strdup(error_id); + } + if (strcmp(error_id, "org.openSUSE.rebootmgr.NoEntryFound") == 0) + return -ENOENT; + else + return -EIO; + } + + *boottime = p.boottime; + return 0; +} + +struct rotate { + bool success; + char *error; + uint64_t entries; + char *backup_name; +}; + +static void +rotate_free (struct rotate *var) +{ + var->backup_name = mfree(var->backup_name); + var->error = mfree(var->error); +} + +int +varlink_rotate (const int days, char **backup_name, uint64_t *entries, char **error) +{ + _cleanup_(rotate_free) struct rotate p = { + .success = false, + .error = NULL, + .entries = 0, + .backup_name = NULL + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct rotate, success), 0 }, + { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct rotate, error), 0 }, + { "Entries", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct rotate, entries), 0 }, + { "BackupName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct rotate, backup_name), 0 }, + {} + }; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + sd_json_variant *result; + int r; + + r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET_WRITER, error); + if (r < 0) + return r; + + r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("Days", SD_JSON_BUILD_INTEGER(days))); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to build JSON data: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + const char *error_id; + r = sd_varlink_call(link, "org.openSUSE.wtmpdb.Rotate", params, &result, &error_id); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to call Rotate method: %s", + strerror(-r)) < 0) + *error = strdup ("Out of memory"); + return r; + } + + /* dispatch before checking error_id, we may need the result for the error + message */ + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + { + if (error) + if (asprintf (error, "Failed to parse JSON answer: %s", + strerror(-r)) < 0) + *error = strdup("Out of memory"); + return r; + } + + if (error_id && strlen(error_id) > 0) + { + if (error) + { + if (p.error) + *error = strdup(p.error); + else + *error = strdup(error_id); + } + return -EIO; + } + + if (p.backup_name) + *backup_name = strdup(p.backup_name); + *entries = p.entries; + + return 0; +} + +#endif diff --git a/lib/varlink.h b/lib/varlink.h new file mode 100644 index 0000000..e808bed --- /dev/null +++ b/lib/varlink.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2024, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +extern int64_t varlink_login (int type, const char *user, + uint64_t usec_login, const char *tty, + const char *rhost, const char *service, + char **error); +extern int varlink_logout (int64_t id, uint64_t usec_logout, char **error); +extern int64_t varlink_get_id (const char *tty, char **error); +extern int varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, + char **azColName), + char **error); +extern int varlink_get_boottime (uint64_t *boottime, char **error); +extern int varlink_rotate (const int days, char **wtmpdb_name, + uint64_t *entries, char **error); diff --git a/meson.build b/meson.build index ef1578c..2227afe 100644 --- a/meson.build +++ b/meson.build @@ -12,11 +12,12 @@ project( 'b_lto=true', 'warning_level=3',], license : ['BSD-2-Clause',], - version : '0.13.0', + version : '0.50.0', ) conf = configuration_data() -conf.set_quoted('PROJECT_VERSION', meson.project_version()) +conf.set_quoted('VERSION', meson.project_version()) +conf.set_quoted('PACKAGE', meson.project_name()) cc = meson.get_compiler('c') pkg = import('pkgconfig') @@ -112,7 +113,7 @@ else endif conf.set10('HAVE_SYSTEMD', have_systemd) -libwtmpdb_c = files('lib/libwtmpdb.c', 'lib/logwtmpdb.c', 'lib/sqlite.c') +libwtmpdb_c = files('lib/libwtmpdb.c', 'lib/logwtmpdb.c', 'lib/sqlite.c', 'lib/varlink.c', 'lib/mkdir_p.c') libwtmpdb_map = 'lib/libwtmpdb.map' libwtmpdb_map_version = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), libwtmpdb_map) @@ -127,7 +128,7 @@ libwtmpdb = shared_library( link_args : ['-shared', libwtmpdb_map_version], link_depends : libwtmpdb_map, - dependencies : [libsqlite3], + dependencies : [libsqlite3, libsystemd], install : true, version : meson.project_version(), soversion : '0' @@ -155,6 +156,14 @@ pam_wtmpdb = shared_library( ) wtmpdb_c = ['src/wtmpdb.c'] +wtmpdbd_c = ['src/wtmpdbd.c', 'src/varlink-org.openSUSE.wtmpdb.c', 'lib/mkdir_p.c'] + +executable('wtmpdbd', + wtmpdbd_c, + include_directories : inc, + link_with : libwtmpdb, + dependencies : [libsystemd], + install : true) executable('wtmpdb', wtmpdb_c, diff --git a/src/varlink-org.openSUSE.wtmpdb.c b/src/varlink-org.openSUSE.wtmpdb.c new file mode 100644 index 0000000..da3957b --- /dev/null +++ b/src/varlink-org.openSUSE.wtmpdb.c @@ -0,0 +1,93 @@ +//SPDX-License-Identifier: GPL-2.0-or-later + +#include "varlink-org.openSUSE.wtmpdb.h" + +static SD_VARLINK_DEFINE_STRUCT_TYPE(WtmpdbEntry, + SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(User, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(Login, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(Logout, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + Login, + SD_VARLINK_FIELD_COMMENT("Request to add a login record"), + SD_VARLINK_DEFINE_INPUT(Type, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_INPUT(User, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_INPUT(LoginTime, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_INPUT(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(ID, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + Logout, + SD_VARLINK_FIELD_COMMENT("Request to close a login record with logout time"), + SD_VARLINK_DEFINE_INPUT(ID, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_INPUT(LogoutTime, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + GetID, + SD_VARLINK_FIELD_COMMENT("Get ID for active entry on TTY"), + SD_VARLINK_DEFINE_INPUT(TTY, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_OUTPUT(ID, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + GetBootTime, + SD_VARLINK_FIELD_COMMENT("Get time of last boot"), + SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_OUTPUT(BootTime, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + ReadAll, + SD_VARLINK_FIELD_COMMENT("Get all entries from the database"), + SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Data, WtmpdbEntry, SD_VARLINK_ARRAY | SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + Rotate, + SD_VARLINK_FIELD_COMMENT("Request to rotate database"), + SD_VARLINK_DEFINE_INPUT(Days, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_OUTPUT(Entries, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(BackupName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + Quit, + SD_VARLINK_FIELD_COMMENT("Stop the daemon"), + SD_VARLINK_DEFINE_INPUT(ExitCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0)); + +static SD_VARLINK_DEFINE_ERROR(NoEntryFound); +static SD_VARLINK_DEFINE_ERROR(InternalError); + +SD_VARLINK_DEFINE_INTERFACE( + org_openSUSE_wtmpdb, + "org.openSUSE.wtmpdb", + SD_VARLINK_INTERFACE_COMMENT("Wtmpdbd control APIs"), + SD_VARLINK_SYMBOL_COMMENT("Add login entry"), + &vl_method_Login, + SD_VARLINK_SYMBOL_COMMENT("Close login entry with logout time"), + &vl_method_Logout, + SD_VARLINK_SYMBOL_COMMENT("Get ID for open login entry on TTY"), + &vl_method_GetID, + SD_VARLINK_SYMBOL_COMMENT("Get last boot time"), + &vl_method_GetBootTime, + SD_VARLINK_SYMBOL_COMMENT("Get all entries from database"), + &vl_method_ReadAll, + SD_VARLINK_SYMBOL_COMMENT("Stop the daemon"), + &vl_method_Quit, + SD_VARLINK_SYMBOL_COMMENT("No entry found"), + &vl_error_NoEntryFound, + SD_VARLINK_SYMBOL_COMMENT("Internal Error"), + &vl_error_InternalError); diff --git a/src/varlink-org.openSUSE.wtmpdb.h b/src/varlink-org.openSUSE.wtmpdb.h new file mode 100644 index 0000000..1a91163 --- /dev/null +++ b/src/varlink-org.openSUSE.wtmpdb.h @@ -0,0 +1,7 @@ +//SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +extern const sd_varlink_interface vl_interface_org_openSUSE_wtmpdb; diff --git a/src/wtmpdb.c b/src/wtmpdb.c index fb5d585..3e71c58 100644 --- a/src/wtmpdb.c +++ b/src/wtmpdb.c @@ -1122,7 +1122,7 @@ main (int argc, char **argv) usage (EXIT_SUCCESS); break; case 'v': - printf ("wtmpdb %s\n", PROJECT_VERSION); + printf ("wtmpdb %s\n", VERSION); break; default: usage (EXIT_FAILURE); diff --git a/src/wtmpdbd.c b/src/wtmpdbd.c new file mode 100644 index 0000000..69d82d0 --- /dev/null +++ b/src/wtmpdbd.c @@ -0,0 +1,701 @@ +//SPDX-License-Identifier: GPL-2.0-or-later + +/* Copyright (c) 2024 Thorsten Kukuk + Author: Thorsten Kukuk + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "basics.h" +#include "wtmpdb.h" +#include "mkdir_p.h" + +#include "varlink-org.openSUSE.wtmpdb.h" + +static int verbose_flag = 0; +static int debug_flag = 0; + +static void +log_msg (int priority, const char *fmt, ...) +{ + static int is_tty = -1; + + if (is_tty == -1) + is_tty = isatty (STDOUT_FILENO); + + va_list ap; + + va_start (ap, fmt); + + if (is_tty || debug_flag) + { + if (priority == LOG_ERR) + { + vfprintf (stderr, fmt, ap); + fputc ('\n', stderr); + } + else + { + vprintf (fmt, ap); + putchar ('\n'); + } + } + else + sd_journal_printv (priority, fmt, ap); + + va_end (ap); +} + + +struct login_record { + int type; + char *user; + uint64_t usec_login; + char *tty; + char *rhost; + char *service; +}; + +static void +login_record_free (struct login_record *var) +{ + var->user = mfree(var->user); + var->tty = mfree(var->tty); + var->rhost = mfree(var->rhost); + var->service = mfree(var->service); +} + +static int +vl_method_login(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + _cleanup_(login_record_free) struct login_record p = { + .type = -1, + .user = NULL, + .usec_login = 0, + .tty = NULL, + .rhost = NULL, + .service = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "Type", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct login_record, type), SD_JSON_MANDATORY }, + { "User", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct login_record, user), SD_JSON_MANDATORY }, + { "LoginTime", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct login_record, usec_login), SD_JSON_MANDATORY }, + { "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct login_record, tty), 0 }, + { "RemoteHost", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct login_record, rhost), 0 }, + { "Service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct login_record, service), 0 }, + {} + }; + int64_t id = -1; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"Login\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + { + log_msg(LOG_ERR, "Add login record request: varlink dispatch failed: %s", strerror (-r)); + return r; + } + + if (debug_flag) + { + log_msg(LOG_DEBUG, "Add login record: %i, %s, %li, %s, %s, %s", + p.type, p.user, p.usec_login, p.tty, p.rhost, p.service); + } + else + { + _cleanup_(freep) char *error = NULL; + + id = wtmpdb_login (_PATH_WTMPDB, p.type, p.user, p.usec_login, p.tty, p.rhost, p.service, &error); + if (id < 0 || error != NULL) + { + log_msg(LOG_ERR, "Get ID request from db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.InternalError", + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error)); + } + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("ID", id)); +} + +static int +vl_method_logout(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + struct p { + int64_t id; + uint64_t usec_logout; + } p = { + .id = -1, + .usec_logout = 0, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "ID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct p, id), SD_JSON_MANDATORY }, + { "LogoutTime", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct p, usec_logout), SD_JSON_MANDATORY }, + {} + }; + _cleanup_(freep) char *error = NULL; + int64_t id = -1; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"Logout\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + { + log_msg(LOG_ERR, "Logout request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + if (debug_flag) + log_msg(LOG_DEBUG, "Logout for entry '%li' at time '%lu' requested", p.id, p.usec_logout); + + id = wtmpdb_logout (_PATH_WTMPDB, p.id, p.usec_logout, &error); + if (id < 0 || error != NULL) + { + /* let wtmpdb_logout return better error codes, e.g. not found vs real error */ + log_msg(LOG_ERR, "Logout request from db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.InternalError", + SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error)); + + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); +} + +struct get_id { + char *tty; +}; + +static void +get_id_free (struct get_id *var) +{ + var->tty = mfree(var->tty); +} + +static int +vl_method_get_id(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + _cleanup_(get_id_free) struct get_id p = { + .tty = NULL, + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct get_id, tty), SD_JSON_MANDATORY }, + {} + }; + _cleanup_(freep) char *error = NULL; + int64_t id = -1; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"GetID\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + { + log_msg(LOG_ERR, "Get ID request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + if (debug_flag) + log_msg(LOG_DEBUG, "ID for entry on tty '%s' requested", p.tty); + + id = wtmpdb_get_id (_PATH_WTMPDB, p.tty, &error); + if (id < 0 || error != NULL) + { + log_msg(LOG_ERR, "Get ID request from db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.NoEntryFound", + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error)); + + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("ID", id)); +} + +static int +vl_method_get_boottime(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + static const sd_json_dispatch_field dispatch_table[] = { + {} + }; + _cleanup_(freep) char *error = NULL; + uint64_t boottime = 0; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"GetBootTime\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL); + if (r != 0) + { + log_msg(LOG_ERR, "Get boottime request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + boottime = wtmpdb_get_boottime (_PATH_WTMPDB, &error); + if (boottime == 0 || error != NULL) + { + log_msg(LOG_ERR, "Get boottime from db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.NoEntryFound", + SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error)); + + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), + SD_JSON_BUILD_PAIR_INTEGER("BootTime", boottime)); +} + +static int incomplete = 0; + +static int +wtmpdb_cb_func (void *u, int argc, char **argv, char **azColName) +{ + sd_json_variant **array = u; + char *endptr; + uint64_t logout_t = 0; + int r; + + if (debug_flag) + log_msg(LOG_DEBUG, "wtmpdb_cb_func called for ID %s", argv[0]); + + const int id = atoi (argv[0]); + const int type = atoi (argv[1]); + const char *user = argv[2]; + const char *tty = argv[5]?argv[5]:"?"; + const char *host = argv[6]?argv[6]:""; + const char *service = argv[7]?argv[7]:""; + uint64_t login_t = strtoull(argv[3], &endptr, 10); + if ((errno == ERANGE && login_t == ULLONG_MAX) + || (endptr == argv[3]) || (*endptr != '\0')) + { + log_msg(LOG_ERR, "Invalid numeric time entry for 'login': '%s'\n", argv[3]); + incomplete = 1; + return 0; + } + if (argv[4]) + { + logout_t = strtoull(argv[4], &endptr, 10); + if ((errno == ERANGE && logout_t == ULLONG_MAX) + || (endptr == argv[4]) || (*endptr != '\0')) + { + log_msg(LOG_ERR, "Invalid numeric time entry for 'logout': '%s'\n", argv[4]); + incomplete = 1; + return 0; + } + } + + if (debug_flag) + log_msg(LOG_DEBUG, "ID: %i, Type: %i, User: %s, Login: %lu, Logout: %lu, TTY: %s, RemoteHost: %s, Service: %s", + id, type, user, login_t, logout_t, tty, host, service); + + r = sd_json_variant_append_arraybo(array, + SD_JSON_BUILD_PAIR_INTEGER("ID", id), + SD_JSON_BUILD_PAIR_INTEGER("Type", type), + SD_JSON_BUILD_PAIR_STRING("User", user), + SD_JSON_BUILD_PAIR_INTEGER("Login", login_t), + SD_JSON_BUILD_PAIR_INTEGER("Logout", logout_t), + SD_JSON_BUILD_PAIR_STRING("TTY", tty), + SD_JSON_BUILD_PAIR_STRING("RemoteHost", host), + SD_JSON_BUILD_PAIR_STRING("Service", service)); + if (r < 0) + { + log_msg(LOG_ERR, "Appending array failed: %s", strerror(-r)); + incomplete = 1; + } + + return 0; +} + + + +static int +vl_method_read_all(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + static const sd_json_dispatch_field dispatch_table[] = { + {} + }; + _cleanup_(freep) char *error = NULL; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"ReadAll\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL); + if (r != 0) + { + log_msg(LOG_ERR, "Get all entries request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + incomplete = 0; + r = wtmpdb_read_all_v2 (_PATH_WTMPDB, &wtmpdb_cb_func, (void *)&array, &error); + if (r < 0 || error != NULL || incomplete) + { + log_msg(LOG_ERR, "Get all entries from db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.InternalError", + SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error?error:"unknown")); + + } + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), + SD_JSON_BUILD_PAIR_VARIANT("Data", array)); +} + +static int +vl_method_rotate(sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void _unused_(*userdata)) +{ + struct p { + int days; + } p = { + .days = -1 + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "Days", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct p, days), SD_JSON_MANDATORY }, + {} + }; + _cleanup_(freep) char *error = NULL; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"Rotate\" called..."); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + { + log_msg(LOG_ERR, "Rotate request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + if (debug_flag) + log_msg(LOG_DEBUG, "Rotate of database for entries older than '%i' days requested", p.days); + + _cleanup_(freep) char *backup = NULL; + uint64_t entries = 0; + r = wtmpdb_rotate (_PATH_WTMPDB, p.days, &error, &backup, &entries); + if (r < 0 || error != NULL) + { + log_msg(LOG_ERR, "Rotate db failed: %s", error); + return sd_varlink_errorbo(link, "org.openSUSE.rebootmgr.NoEntryFound", + SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), + SD_JSON_BUILD_PAIR_STRING("ErrorMsg", error)); + } + + /* XXX make this nicer, build reply on demand */ + if (backup) + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), + SD_JSON_BUILD_PAIR_STRING("BackupName", backup), + SD_JSON_BUILD_PAIR_INTEGER("Entries", entries)); + else + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), + SD_JSON_BUILD_PAIR_INTEGER("Entries", entries)); +} + +static int +vl_method_quit (sd_varlink *link, sd_json_variant *parameters, + sd_varlink_method_flags_t _unused_(flags), + void *userdata) +{ + struct p { + int code; + } p = { + .code = 0 + }; + static const sd_json_dispatch_field dispatch_table[] = { + { "ExitCode", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct p, code), 0 }, + {} + }; + sd_event *loop = userdata; + int r; + + if (verbose_flag) + log_msg (LOG_INFO, "Varlink method \"Quit\" called..."); + + r = sd_varlink_dispatch (link, parameters, dispatch_table, /* userdata= */ NULL); + if (r != 0) + { + log_msg (LOG_ERR, "Quit request: varlik dispatch failed: %s", strerror (-r)); + return r; + } + + r = sd_event_exit (loop, p.code); + if (r != 0) + { + log_msg (LOG_ERR, "Quit request: disabling event loop failed: %s", + strerror (-r)); + return sd_varlink_errorbo(link, "org.openSUSE.wtmpdb.InternalError", + SD_JSON_BUILD_PAIR_BOOLEAN("Success", false)); + } + + return sd_varlink_replybo (link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); +} + +/* Send a messages to systemd daemon, that inicialization of daemon + is finished and daemon is ready to accept connections. */ +static void +announce_ready (void) +{ + int r = sd_notify (0, "READY=1\n" + "STATUS=Processing requests..."); + if (r < 0) + log_msg (LOG_ERR, "sd_notify() failed: %s", strerror(-r)); +} + + +static int +varlink_server_loop (sd_varlink_server *reader, sd_varlink_server *writer) +{ + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + r = sd_event_new (&event); + if (r < 0) + return r; + + sd_varlink_server_set_userdata (reader, event); + sd_varlink_server_set_userdata (writer, event); + + r = sd_varlink_server_attach_event (reader, event, 0); + if (r < 0) + return r; + + r = sd_varlink_server_attach_event (writer, event, 0); + if (r < 0) + return r; + + r = sd_varlink_server_listen_auto (reader); + if (r < 0) + return r; + r = sd_varlink_server_listen_auto (writer); + if (r < 0) + return r; + + announce_ready(); + + return sd_event_loop (event); +} + +static int +init_varlink_server (sd_varlink_server **varlink_server, int flags) +{ + int r; + + r = sd_varlink_server_new (varlink_server, flags); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to allocate varlink server: %s", + strerror (-r)); + return r; + } + + r = sd_varlink_server_set_description (*varlink_server, "wtmpdbd"); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to set varlink server description: %s", + strerror (-r)); + return r; + } + + r = sd_varlink_server_add_interface (*varlink_server, &vl_interface_org_openSUSE_wtmpdb); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to add Varlink interface: %s", + strerror (-r)); + return r; + } + + r = sd_varlink_server_set_exit_on_idle (*varlink_server, false); + if (r < 0) + return r; + + r = sd_varlink_server_set_info (*varlink_server, NULL, PACKAGE" (wtmpdbd)", + VERSION, "https://github.com/thkukuk/wtmpdb"); + if (r < 0) + return r; + + return 0; +} + +static int +run_varlink (void) +{ + int r; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_reader = NULL; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_writer = NULL; + + r = mkdir_p(_VARLINK_WTMPDB_SOCKET_DIR, 0755); + if (r < 0) + { + log_msg(LOG_ERR, "Failed to create directory '"_VARLINK_WTMPDB_SOCKET_DIR"' for Varlink socket: %s", + strerror(-r)); + return r; + } + + r = init_varlink_server(&varlink_reader, 0|SD_VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return r; + + r = sd_varlink_server_bind_method_many (varlink_reader, + "org.openSUSE.wtmpdb.GetBootTime", vl_method_get_boottime, + "org.openSUSE.wtmpdb.GetID", vl_method_get_id, + "org.openSUSE.wtmpdb.ReadAll", vl_method_read_all); + if (r < 0) + { + log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", + strerror(-r)); + return r; + } + + r = sd_varlink_server_listen_address(varlink_reader, _VARLINK_WTMPDB_SOCKET_READER, 0666); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to bind to Varlink socket (reader): %s", strerror (-r)); + return r; + } + + r = init_varlink_server(&varlink_writer, (debug_flag?0:SD_VARLINK_SERVER_ROOT_ONLY)|SD_VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return r; + + r = sd_varlink_server_bind_method_many (varlink_writer, + "org.openSUSE.wtmpdb.Login", vl_method_login, + "org.openSUSE.wtmpdb.Logout", vl_method_logout, + "org.openSUSE.wtmpdb.Rotate", vl_method_rotate, + "org.openSUSE.wtmpdb.Quit", vl_method_quit); + if (r < 0) + { + log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", + strerror(-r)); + return r; + } + + r = sd_varlink_server_listen_address(varlink_writer, _VARLINK_WTMPDB_SOCKET_WRITER, 0600); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to bind to Varlink socket (writer): %s", strerror (-r)); + return r; + } + + r = varlink_server_loop (varlink_reader, varlink_writer); + if (r < 0) + { + log_msg (LOG_ERR, "Failed to run Varlink event loop: %s", + strerror (-r)); + return r; + } + + return 0; +} + +static void +print_help (void) +{ + log_msg (LOG_INFO, "wtmpdbd - manage wtmpdb"); + + log_msg (LOG_INFO, " -d,--debug Debug mode, no changes done"); + log_msg (LOG_INFO, " -v,--verbose Verbose logging"); + log_msg (LOG_INFO, " -?, --help Give this help list"); + log_msg (LOG_INFO, " --version Print program version"); +} + +int +main (int argc, char **argv) +{ + int r; + + while (1) + { + int c; + int option_index = 0; + static struct option long_options[] = + { + {"debug", no_argument, NULL, 'd'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, '\255'}, + {"usage", no_argument, NULL, '?'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, '\0'} + }; + + + c = getopt_long (argc, argv, "dvh?", long_options, &option_index); + if (c == (-1)) + break; + switch (c) + { + case 'd': + debug_flag = 1; + verbose_flag = 1; + break; + case '?': + case 'h': + print_help (); + return 0; + case 'v': + verbose_flag = 1; + break; + case '\255': + fprintf (stdout, "wtmpdbd (%s) %s\n", PACKAGE, VERSION); + return 0; + default: + print_help (); + return 1; + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + { + fprintf (stderr, "Try `wtmpdbd --help' for more information.\n"); + return 1; + } + + if (verbose_flag) + log_msg (LOG_INFO, "Starting wtmpdbd (%s) %s...", PACKAGE, VERSION); + + r = run_varlink (); + if (r < 0) + log_msg (LOG_ERR, "ERROR: varlink loop failed: %s", strerror (-r)); + + return -r; +} diff --git a/tests/meson.build b/tests/meson.build index 8677450..f1e2892 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -18,3 +18,14 @@ tst_login_logout = executable ('tst-login-logout', 'tst-login-logout.c', include_directories : inc, link_with : libwtmpdb) test('tst-login-logout', tst_login_logout) + +tst_get_id = executable ('tst-get_id', 'tst-get_id.c', + include_directories : inc, + link_with : libwtmpdb) +test('tst-get_id', tst_get_id) + +tst_varlink = executable ('tst-varlink', 'tst-varlink.c', + include_directories : inc, + link_with : libwtmpdb) +test('tst-varlink', tst_varlink) + diff --git a/tests/tst-get_id.c b/tests/tst-get_id.c new file mode 100644 index 0000000..2273cb0 --- /dev/null +++ b/tests/tst-get_id.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2023, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Test case: + Try to get the ID for an non-existing tty +*/ + +#include +#include +#include +#include +#include + +#include "wtmpdb.h" + +#include "basics.h" + +int +main(void) +{ + _cleanup_(freep) char *error = NULL; + int64_t id = wtmpdb_get_id (NULL, "ttyXYZ-doesnotexist", &error); + + if (id == -2) + { + printf ("wtmpdb_get_id returned expected: %li, '%s'\n", + id, error); + return 0; + } + + fprintf (stderr, "wtmpdb_get_id returns '%ld' with error message: %s\n", + id, error); + return 1; +} diff --git a/tests/tst-varlink.c b/tests/tst-varlink.c new file mode 100644 index 0000000..cd008f4 --- /dev/null +++ b/tests/tst-varlink.c @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: BSD-2-Clause + + Copyright (c) 2023, Thorsten Kukuk + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Test case: + Create login entry, search for ID, add logout time, get boottime. +*/ + +#include +#include +#include +#include +#include + +#include "basics.h" +#include "wtmpdb.h" + +static void +format_time (char *dst, size_t dstlen, uint64_t time) +{ + time_t t = (time_t)time; + snprintf (dst, dstlen, "%s", ctime (&t)); + dst[strlen (dst)-1] = '\0'; /* Remove trailing '\n' */ +} + +int +main(void) +{ + const char *user = "wtmpdb-test"; + const char *tty = "ttyX"; + const char *rhost = "remote-host"; + const char *service = "sshd"; + char *error = NULL; + int64_t id; + struct timespec ts; + uint64_t login_t; + uint64_t logout_t; + + clock_gettime (CLOCK_REALTIME, &ts); + login_t = wtmpdb_timespec2usec (ts); + + if ((id = wtmpdb_login (NULL, USER_PROCESS, user, + login_t, tty, rhost, service, &error)) < 0) + { + if (error) + { + fprintf (stderr, "wtmpdb_login: %s\n", error); + free (error); + } + else + fprintf (stderr, "wtmpdb_login failed (%li)\n", id); + return 1; + } + printf ("wtmpdb_login id: %li\n", id); + + /* wtmpdb_get_id should return the same ID as wtmpdb_login */ + int64_t newid; + if ((newid = wtmpdb_get_id (NULL, tty, &error)) < 0) + { + if (error) + { + fprintf (stderr, "wtmpdb_get_id: %s\n", error); + free (error); + } + else + fprintf (stderr, "wtmpdb_get_id failed\n"); + return 1; + } + printf ("wtmpdb_get_id: %li\n", newid); + + if (newid != id) + { + fprintf (stderr, "IDs don't match: %li != %li\n", id, newid); + return 1; + } + + clock_gettime (CLOCK_REALTIME, &ts); + logout_t = wtmpdb_timespec2usec (ts); + + if (wtmpdb_logout (NULL, id, logout_t, &error) != 0) + { + if (error) + { + fprintf (stderr, "wtmpdb_logout: %s\n", error); + free (error); + } + else + fprintf (stderr, "wtmpdb_logout failed\n"); + return 1; + } + + uint64_t boottime = wtmpdb_get_boottime (NULL, &error); + if (boottime == 0 || error != NULL) + { + if (error) + { + fprintf (stderr, "wtmpdb_get_boottime: %s\n", error); + free (error); + } + else + fprintf (stderr, "wtmpdb_get_boottime failed\n"); + return 1; + } + else + { + char timebuf[32]; + format_time (timebuf, sizeof (timebuf), boottime/USEC_PER_SEC); + + printf ("wtmpdb_get_boottime: %s\n", timebuf); + } + + _cleanup_(freep) char *backup = NULL; + uint64_t entries = 0; + if (wtmpdb_rotate (NULL, 30, &error, &backup, &entries) < 0) + { + if (error) + { + fprintf (stderr, "wtmpdb_rotate: %s\n", error); + free (error); + } + else + fprintf (stderr, "wtmpdb_rotate failed\n"); + return 1; + } + else + printf ("wtmpdb_rotate moved %lu entries into %s\n", entries, backup); + + return 0; +}