pax_global_header00006660000000000000000000000064147752210300014513gustar00rootroot0000000000000052 comment=edb8638a616e720616a1564ec395275b09295b8e thkukuk-wtmpdb-edb8638/000077500000000000000000000000001477522103000151215ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/.github/000077500000000000000000000000001477522103000164615ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/.github/workflows/000077500000000000000000000000001477522103000205165ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/.github/workflows/ci-opensuse.yml000066400000000000000000000013441477522103000234750ustar00rootroot00000000000000name: openSUSE build & test on: [push, pull_request] jobs: build: runs-on: ubuntu-latest container: registry.opensuse.org/opensuse/tumbleweed:latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install devel packages run: | zypper ref zypper --non-interactive in --no-recommends meson gcc libeconf-devel systemd-devel sqlite3-devel audit-devel pam-devel docbook5-xsl-stylesheets valgrind - name: Setup meson run: meson setup build --auto-features=enabled - name: Compile code run: meson compile -v -C build - name: Run tests run: meson test -v -C build --wrap='valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1' thkukuk-wtmpdb-edb8638/.github/workflows/meson-sanitizer.yml000066400000000000000000000012021477522103000243630ustar00rootroot00000000000000name: meson sanitizer on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - name: Install pam-devel run: sudo apt-get install libpam0g-dev libsqlite3-dev - uses: actions/checkout@v3 - uses: BSFishy/meson-build@v1.0.3 with: action: build directory: build setup-options: -Db_sanitize=address,undefined options: --verbose meson-version: 0.61.4 - uses: BSFishy/meson-build@v1.0.3 with: action: test directory: build setup-options: -Db_sanitize=address,undefined options: --verbose meson-version: 0.61.4 thkukuk-wtmpdb-edb8638/.github/workflows/meson.yml000066400000000000000000000007471477522103000223720ustar00rootroot00000000000000name: meson build & test on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - name: Install pam-devel run: sudo apt-get install libpam0g-dev libsqlite3-dev - uses: actions/checkout@v3 - uses: BSFishy/meson-build@v1.0.3 with: action: build options: --verbose meson-version: 0.61.4 - uses: BSFishy/meson-build@v1.0.3 with: action: test options: --verbose meson-version: 0.61.4 thkukuk-wtmpdb-edb8638/.gitignore000066400000000000000000000001641477522103000171120ustar00rootroot00000000000000# Object files *.o *.ko *.obj *.elf # Libraries *.lib *.a *.la *.lo # Shared objects *.so *.so.* # Misc build *~ thkukuk-wtmpdb-edb8638/INSTALL.md000066400000000000000000000005731477522103000165560ustar00rootroot00000000000000# Building and installing wtmpdb ## Building with Meson wtmpdb requires a relatively recent version of Meson. Building with Meson is quite simple: ```shell $ meson setup build $ meson compile -C build $ meson test -C build $ sudo meson install -C build ``` If you want to build with the address sanitizer enabled, add `-Db_sanitize=address` as an argument to `meson build`. thkukuk-wtmpdb-edb8638/LICENSE000066400000000000000000000024271477522103000161330ustar00rootroot00000000000000BSD 2-Clause License 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. thkukuk-wtmpdb-edb8638/NEWS000066400000000000000000000050141477522103000156200ustar00rootroot00000000000000Version 0.73.0 * Expand accepted time format options (-p) * Use documented -t short option for last --until * Avoid PATH_MAX * hurd: compat for lack of CLOCK_BOOTTIME Version 0.72.0 * Implement import function for old wtmp files * Fix rotate tests * Fixes for ix86 Version 0.71.0 * Fix installation of all wtmpdbd man page variants * Accept classic 'last -N' form for max entries * Fix: zero struct tm before calling strptime() Version 0.70.0 * wtmpdbd: Exit after 30 seconds of idle time in socket activation mode * wtmpdbd: add Ping, SetLogLevel and GetEnvironment as varlink method * Merge reader/write socket to one generic one * Harden wtmpdbd.service * wtmpdb: Implement json output for last verb Version 0.50.0 * Introduce wtmpdbd as single daemon accessing the database (#12) Version 0.13.0 * Fix variable overflow on 32bit systems and check for this (#15) Version 0.12.0 * boot: Query systemd if soft-reboot was done Version 0.11.0 * last: add support for time-format option Version 0.10.0 * last: support matching for username and/or tty Version 0.9.3 * wtmpdb last: don't print date in the future if there is no db entry Version 0.9.2 * Increase busy timeout DB access Version 0.9.1 * Require meson 0.61.0 or newer * Fix printf format specifier on 32bit (#8) Version 0.9.0 * Fix lot of 32bit/64bit signed/unsiged int conversations * Try to autodetect systemctl soft-reboot and use current time instead of kernel boot time. Version 0.8.0 * Fix linking with clang * wtmdb boottime: show boot time Version 0.7.1 * wtmpdb last: Support "YYYY-MM-DD", "today" and "yesterday" as time option Version 0.7.0 * wtmpdb rotate: use sqlite3_bind_* internal * wtmpdb last: Implement -x, -d, -i and -w options Version 0.6.0 * wtmpdb rotate: move old log entries into wtmpdb_.db Version 0.5.0 * Use uint64_t instead of usec_t to avoid conflicts with other projects * wtmpdb boot: more accurate calculation of boot time Version 0.4.0 * libwtmpdb: Use project version for library version * libwtmpdb: Always use _PATH_WTMPDB as fallback Version 0.3.0 * pam_wtmpdb: Add skip_if option * Add manual pages * wtmpdb last: fix wtmp begins timestamp if no matching entry was found * wtmpdb last: Add --since and --until options * Add compat symlink for "last" * wtmpdb last: add --present option * wtmpdb last: implement -n/--limit * pam_wtmpdb: Try XDG_VTNR if PAM_TTY is not a tty Version 0.2.0 * pam_lastlog: support PAM_XDISPLAY * wtmpdb last: show PAM service on request * wtmpdb: log audit records for boot/shutdown Version 0.1.0 * First release thkukuk-wtmpdb-edb8638/README.md000066400000000000000000000121101477522103000163730ustar00rootroot00000000000000# wtmpdb **Y2038 safe version of wtmp** ## Background `last` reports the login and logout times of users and when the machine got rebooted. The standard `/var/log/wtmp` implementation using `utmp.h` from glibc uses a **32bit** **time_t** in `struct utmp` on bi-arch systems like x86-64 (so which can execute 64bit and 32bit binaries). So even if you have a pure 64bit system, on many architectures using glibc you have a Y2038 problem. For background on the Y2038 problem (32bit time_t counter will overflow) I suggest to start with the wikipedia [Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) article. There is also a more [technical document](https://github.com/thkukuk/utmpx/blob/main/Y2038.md), describing the problem in more detail, which also contains a list of affected packages. And a more highlevel blog "[Y2038, glibc and wtmp on 64bit architectures](https://www.thkukuk.de/blog/Y2038_glibc_wtmp_64bit/)" ## Functionality The main features of `wtmpdb` are: * It's using sqlite3 as database backend. * Data is mainly collected via a PAM module, so that every tool can make use of it, without modifying existing packages. For cases where this is not possible, there is a library `libwtmpdb`. * The `wtmpdb last` output is as compatible as possible with the old `last` implementation, but not all options are yet supported. For compatibility reasons, a symlink `last` pointing to `wtmpdb` can be created. * There is an optional `wtmpdbd` daemon for central management of the sqlite3 database using sd-varlink for communication with `libwtmpdb`. **IMPORTANT** To be Y2038 safe on 32bit architectures, the binaries needs to be build with a **64bit time_t**. This should be the standard on 64bit architectures. The package constists of a library, PAM module, a commandline interface and an optional daemon: * `libwtmpdb.so.0` contains all high level functions to manage the data. * `pam_wtmpdb.so` stores the login and logout time of an user into the database. * `wtmpdb` is used to add reboot and shutdown entries and to display existing entries (like `last`). * `wtmpdbd` is used to manage the database in a secure way. By default the database will be written as `/var/lib/wtmpdb/wtmp.db`. ## Configuration The `pam_wtmpdb.so` module will be added in the `session` section of the service, which should create wtmp entries. On openSUSE Tumbleweed and MicroOS, the following line needs be added to `/etc/pam.d/postlogin-session`: ``` session optional pam_wtmpdb.so ``` This line will create a new entry in the database for every user if an application calls the PAM framework. ### OpenSSH OpenSSH does not provide the TTY to PAM modules, but the TTY value is important to identify the correct entry. For this reasons, an openssh version with wtmpdb is required (should be openssh >= 10.0) or the wtmpdb support needs to be backported. The PAM module (`pam_wtmpdb.so`) needs to be removed for the sshd service, or if it is configured in a "common" section, disabled: ``` session optional pam_wtmpdb.so skip_if=sshd ``` ## Design ### Database sqlite3 is used for the database. The table `wtmp` contains the following columns: * `ID` is the primary identifier for an entry and will be automatically assigned by sqlite. * `Type` defines which kind of entry this is. Currently supported are: * `BOOT_TIME` is the time of system boot and shutdown * `RUNLEVEL` is for non-systemd systems * `USER_PROCESS` contains the normal user login and logout data * `User` is a mandatory field containing the login name or "reboot" for boot/shutdown entries * `Login` is the login time of the user in microseconds since 1.1.1970. * `Logout` is the logout time of the user in microseconds since 1.1.1970. * `TTY` is the tty or "~" for the "reboot" entry. If this entry got created via the PAM module, this could also contain some generic strings like `ssh` for applications, which fake the PAM_TTY entry. * `RemoteHost` is the remote hostname from which the user did connect or the content of the display variable. * `Service` is the PAM service which created the entry. ### API The `libwtmpdb` library provides the following main functions beside some helper functions: * `logwtmpdb()` is very similar to `logwtmp.3` to make it easier to convert applications. * `wtmpdb_login()` is the function to create a new login entry. * `wtmpdb_logout()` is the function to add the logout time to an existing entry. * `wtmpdb_read_all()` iterates over all entries and calls a callback function with every single entry. ### Command line tool The `wtmpdb` command supports the following tasks: * `wtmpdb last` is a replacement for `last`. * `wtmpdb boot` creates a boot entry. * `wtmpdb shutdown` add the shutdown time to the current boot entry. ### Daemon The `wtmpdbd` daemon provides a varlink interface for `libwtmpdb`. This allows to secure the database so that only root has access to it. The daemon will be started about two systemd socket units. ### systemd service * `wtmpdb-update-boot.service` will record the boot and shutdown times of a service. * `wtmpdbd-reader.socket` and `wtmpdbd-writer.socket` will start `wtmpdbd` on demand. thkukuk-wtmpdb-edb8638/include/000077500000000000000000000000001477522103000165445ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/include/wtmpdb.h000066400000000000000000000063461477522103000202230ustar00rootroot00000000000000/* 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. */ #pragma once #include #include #define _PATH_WTMPDB "/var/lib/wtmpdb/wtmp.db" #define _VARLINK_WTMPDB_SOCKET_DIR "/run/wtmpdb" #define _VARLINK_WTMPDB_SOCKET _VARLINK_WTMPDB_SOCKET_DIR"/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. */ #define USER_PROCESS 3 /* Normal process. */ #define USEC_INFINITY ((uint64_t) UINT64_MAX) #define NSEC_PER_USEC ((uint64_t) 1000ULL) #define USEC_PER_SEC ((uint64_t) 1000000ULL) #ifdef __cplusplus extern "C" { #endif extern int64_t logwtmpdb (const char *db_path, const char *tty, const char *name, const char *host, const char *service, char **error); extern int64_t 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); extern int wtmpdb_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error); 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_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 */ extern uint64_t wtmpdb_get_boottime (const char *db_path, char **error); /* helper function */ extern int64_t wtmpdb_get_id (const char *db_path, const char *tty, char **error); extern uint64_t wtmpdb_timespec2usec (const struct timespec ts); #ifdef __cplusplus } #endif thkukuk-wtmpdb-edb8638/lib/000077500000000000000000000000001477522103000156675ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/lib/basics.h000066400000000000000000000007001477522103000173010ustar00rootroot00000000000000// 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); } thkukuk-wtmpdb-edb8638/lib/libwtmpdb.c000066400000000000000000000153301477522103000200210ustar00rootroot00000000000000/* 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" #include #include #include #include #include "basics.h" #include "wtmpdb.h" #include "sqlite.h" #include "varlink.h" #if WITH_WTMPDBD static int varlink_is_active = 1; #else static int varlink_is_active = 0; #endif static int varlink_is_enforced = 0; #define VARLINK_CHECKS \ if (varlink_is_enforced == 0 && db_path != NULL && \ strcmp (db_path, "varlink") == 0) \ varlink_is_enforced = 1; \ \ /* we can use varlink only if no specific database is requested */ \ if (varlink_is_enforced || (varlink_is_active && db_path == NULL)) #define VARLINK_IS_NOT_RUNNING(r) (r == -ECONNREFUSED || r == -ENOENT || r == -ECONNRESET || r == -EACCES) /* Add new wtmp entry to db. login timestamp is in usec. Returns 0 on success, -1 on failure. */ int64_t 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) { VARLINK_CHECKS { #if WITH_WTMPDBD int64_t id; id = varlink_login (type, user, usec_login, tty, rhost, service, error); if (id >= 0 || varlink_is_enforced) return id; if (VARLINK_IS_NOT_RUNNING(id)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return id; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } return sqlite_login (db_path?db_path:_PATH_WTMPDB, type, user, usec_login, tty, rhost, service, error); } /* Add logout timestamp to existingentry. logout timestamp is in usec. ID is the return value of wtmpdb_login/logwtmpdb. Returns 0 on success, -1 on failure. */ int wtmpdb_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error) { VARLINK_CHECKS { #if WITH_WTMPDBD int r; r = varlink_logout (id, usec_logout, error); if (r >= 0) return r; if (VARLINK_IS_NOT_RUNNING(id)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return r; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } 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) { VARLINK_CHECKS { #if WITH_WTMPDBD int64_t id; id = varlink_get_id (tty, error); if (id >= 0) return id; if (VARLINK_IS_NOT_RUNNING(id)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return id; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } return sqlite_get_id (db_path?db_path:_PATH_WTMPDB, tty, error); } /* Reads all entries from database and calls the callback function for 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) { VARLINK_CHECKS { #if WITH_WTMPDBD int r; r = varlink_read_all (cb_func, NULL, error); if (r >= 0) return r; if (VARLINK_IS_NOT_RUNNING(r)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return r; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } 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) { VARLINK_CHECKS { #if WITH_WTMPDBD int r; r = varlink_read_all (cb_func, userdata, error); if (r >= 0) return r; if (VARLINK_IS_NOT_RUNNING(r)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return r; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } 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, < 0 on failure. */ int wtmpdb_rotate (const char *db_path, const int days, char **error, char **wtmpdb_name, uint64_t *entries) { VARLINK_CHECKS { #if WITH_WTMPDBD int r; r = varlink_rotate (days, wtmpdb_name, entries, error); if (r >= 0) return r; if (VARLINK_IS_NOT_RUNNING(r)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return r; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } 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) { uint64_t boottime; int r; VARLINK_CHECKS { #if WITH_WTMPDBD r = varlink_get_boottime (&boottime, error); if (r >= 0) return boottime; if (VARLINK_IS_NOT_RUNNING(r)) { varlink_is_active = 0; if (error) *error = mfree (*error); } else return 0; /* return the error if wtmpdbd is active */ #else return -EPROTONOSUPPORT; #endif } r = sqlite_get_boottime (db_path?db_path:_PATH_WTMPDB, &boottime, error); if (r < 0) return 0; else return boottime; } thkukuk-wtmpdb-edb8638/lib/libwtmpdb.map000066400000000000000000000005301477522103000203500ustar00rootroot00000000000000LIBWTMPDB_0.1 { global: logwtmpdb; wtmpdb_login; wtmpdb_logout; wtmpdb_read_all; wtmpdb_timespec2usec; wtmpdb_get_id; local: *; }; LIBWTMPDB_0.7 { global: wtmpdb_rotate; } LIBWTMPDB_0.1; LIBWTMPDB_0.8 { global: wtmpdb_get_boottime; } LIBWTMPDB_0.7; LIBWTMPDB_0.50 { global: wtmpdb_read_all_v2; } LIBWTMPDB_0.8; thkukuk-wtmpdb-edb8638/lib/logwtmpdb.c000066400000000000000000000046521477522103000200410ustar00rootroot00000000000000/* 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. */ #include #include #include #include #include #include "wtmpdb.h" uint64_t wtmpdb_timespec2usec (const struct timespec ts) { if (ts.tv_sec < 0 || ts.tv_nsec < 0) return USEC_INFINITY; if ((uint64_t) ts.tv_sec > (UINT64_MAX - (ts.tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) return UINT64_MAX; return (uint64_t) ts.tv_sec * USEC_PER_SEC + (uint64_t) ts.tv_nsec / NSEC_PER_USEC; } int64_t logwtmpdb (const char *db_path, const char *tty, const char *name, const char *host, const char *service, char **error) { int64_t retval = -1; struct timespec ts; clock_gettime (CLOCK_REALTIME, &ts); uint64_t time = wtmpdb_timespec2usec (ts); if (error) *error = NULL; if (name != NULL && strlen (name) > 0) { /* login */ retval = wtmpdb_login (db_path, USER_PROCESS, name, time, tty, host, service, error); } else { /* logout */ int64_t id = wtmpdb_get_id (db_path, tty, error); retval = wtmpdb_logout (db_path, id, time, error); } return retval; } thkukuk-wtmpdb-edb8638/lib/mkdir_p.c000066400000000000000000000043531477522103000174650ustar00rootroot00000000000000/* 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); } thkukuk-wtmpdb-edb8638/lib/mkdir_p.h000066400000000000000000000026771477522103000175010ustar00rootroot00000000000000/* 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); thkukuk-wtmpdb-edb8638/lib/sqlite.c000066400000000000000000000457061477522103000173500ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause 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: 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" #include #include #include #include #include #include #include #include #include #include #include "wtmpdb.h" #include "sqlite.h" #include "mkdir_p.h" #define TIMEOUT 5000 /* 5 sec */ static void strip_extension(char *in_str) { static const int name_min_len = 1; static const int max_ext_len = 4; /* Check chars starting at end of string to find last '.' */ for (size_t i = strlen(in_str); i > (name_min_len + max_ext_len); i--) { if (in_str[i] == '.') { in_str[i] = '\0'; return; } } } static int open_database_ro (const char *path, sqlite3 **db, char **error) { int r; r = sqlite3_open_v2 (path, db, SQLITE_OPEN_READONLY, NULL); if (r != SQLITE_OK) { if (error) if (asprintf(error, "open_database_ro: Cannot open database (%s): %s", path, sqlite3_errmsg(*db)) < 0) *error = strdup("open_database_ro: Out of memory"); sqlite3_close(*db); *db = NULL; return r; } sqlite3_busy_timeout(*db, TIMEOUT); return 0; } static int open_database_rw (const char *path, sqlite3 **db, char **error) { int r; char *buf = strdup(path); mkdir_p(dirname(buf), 0755); free(buf); #if WITH_WTMPDBD mode_t old_umask = umask(0077); #endif r = sqlite3_open (path, db); #if WITH_WTMPDBD umask (old_umask); #endif if (r != SQLITE_OK) { if (error) if (asprintf (error, "open_database_rw: Cannot create/open database (%s): %s", path, sqlite3_errmsg (*db)) < 0) *error = strdup ("open_database_rw: Out of memory"); sqlite3_close (*db); *db = NULL; return -r; } sqlite3_busy_timeout(*db, TIMEOUT); return 0; } /* Add a new entry. Returns ID (>=0) on success, -1 on failure. */ static int64_t add_entry (sqlite3 *db, int type, const char *user, uint64_t usec_login, const char *tty, const char *rhost, const char *service, char **error) { char *err_msg = NULL; sqlite3_stmt *res; char *sql_table = "CREATE TABLE IF NOT EXISTS wtmp(ID INTEGER PRIMARY KEY, Type INTEGER, User TEXT NOT NULL, Login INTEGER, Logout INTEGER, TTY TEXT, RemoteHost TEXT, Service TEXT) STRICT;"; char *sql_insert = "INSERT INTO wtmp (Type,User,Login,TTY,RemoteHost,Service) VALUES(?,?,?,?,?,?);"; if (sqlite3_exec (db, sql_table, 0, 0, &err_msg) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: SQL error: %s", err_msg) < 0) *error = strdup ("add_entry: Out of memory"); sqlite3_free (err_msg); return -1; } if (sqlite3_prepare_v2 (db, sql_insert, -1, &res, 0) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to execute statement: %s", sqlite3_errmsg (db)) < 0) *error = strdup ("add_entry: Out of memory"); return -1; } if (sqlite3_bind_int (res, 1, type) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for type: %s", sqlite3_errmsg (db)) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } if (sqlite3_bind_text (res, 2, user, -1, SQLITE_STATIC) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for user: %s", sqlite3_errmsg (db)) < 0) *error = strdup ("add_entry: Out of memory"); sqlite3_finalize (res); return -1; } if (sqlite3_bind_int64 (res, 3, usec_login) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for login time: %s", sqlite3_errmsg (db)) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } if (sqlite3_bind_text (res, 4, tty, -1, SQLITE_STATIC) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for tty: %s", sqlite3_errmsg (db)) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } if (sqlite3_bind_text (res, 5, rhost, -1, SQLITE_STATIC) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for rhost: %s", sqlite3_errmsg (db)) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } if (sqlite3_bind_text (res, 6, service, -1, SQLITE_STATIC) != SQLITE_OK) { if (error) if (asprintf (error, "add_entry: Failed to create replace statement for service: %s", sqlite3_errmsg (db)) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } int step = sqlite3_step (res); if (step != SQLITE_DONE) { if (error) if (asprintf (error, "add_entry: Adding an entry did not return SQLITE_DONE: %d", step) < 0) *error = strdup("add_entry: Out of memory"); sqlite3_finalize(res); return -1; } sqlite3_finalize(res); return sqlite3_last_insert_rowid(db); } /* Add new wtmp entry to db. login timestamp is in usec. Returns ID on success, < 0 on failure. */ int64_t sqlite_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) { sqlite3 *db; int64_t id; int r; r = open_database_rw(db_path, &db, error); if (r < 0) return r; id = add_entry(db, type, user, usec_login, tty, rhost, service, error); sqlite3_close(db); return id; } /* Updates logout field. logout timestamp is in usec. Returns 0 on success, < 0 on failure. */ static int update_logout (sqlite3 *db, int64_t id, uint64_t usec_logout, char **error) { sqlite3_stmt *res; char *sql = "UPDATE wtmp SET Logout = ? WHERE ID = ?"; if (sqlite3_prepare_v2 (db, sql, -1, &res, 0) != SQLITE_OK) { if (error) if (asprintf (error, "update_logout: Failed to execute statement: %s", sqlite3_errmsg (db)) < 0) *error = strdup ("update_logout: Out of memory"); return -1; } if (sqlite3_bind_int64 (res, 1, usec_logout) != SQLITE_OK) { if (error) if (asprintf (error, "update_logout: Failed to create update query (logout): %s", sqlite3_errmsg (db)) < 0) *error = strdup("update_logout: Out of memory"); sqlite3_finalize(res); return -1; } if (sqlite3_bind_int64 (res, 2, id) != SQLITE_OK) { if (error) if (asprintf (error, "update_logout: Failed to create update query (ID): %s", sqlite3_errmsg (db)) < 0) *error = strdup("update_logout: Out of memory"); sqlite3_finalize(res); return -1; } int step = sqlite3_step (res); if (step != SQLITE_DONE) { if (error) if (asprintf (error, "update_logout: Updating logout time did not return SQLITE_DONE: %d", step) < 0) *error = strdup("update_logout: Out of memory"); sqlite3_finalize(res); return -1; } int changes; if ((changes = sqlite3_changes (db)) != 1) { if (error) if (asprintf (error, "update_logout: Updated wrong number of rows, expected 1, got %d", changes) < 0) *error = strdup("update_logout: Out of memory"); sqlite3_finalize(res); return -1; } sqlite3_finalize (res); return 0; } /* Add logout timestamp to existingentry. logout timestamp is in usec. ID is the return value of wtmpdb_login/logwtmpdb. Returns 0 on success, < 0 on failure. */ int sqlite_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error) { sqlite3 *db; int r; r = open_database_rw (db_path, &db, error); if (r < 0) return r; r = update_logout(db, id, usec_logout, error); sqlite3_close (db); return r; } static int64_t search_id (sqlite3 *db, const char *tty, char **error) { int64_t id = -1; sqlite3_stmt *res; char *sql = "SELECT ID FROM wtmp WHERE TTY = ? AND Logout IS NULL ORDER BY Login DESC LIMIT 1"; 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) { 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) { r = -ENOMEM; *error = strdup("search_id: Out of memory"); } sqlite3_finalize(res); 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: sqlite3_step returned: %d", step) < 0) { *error = strdup("search_id: Out of memory"); id = -ENOMEM; } } sqlite3_finalize (res); return id; } int64_t sqlite_get_id (const char *db_path, const char *tty, char **error) { sqlite3 *db; int64_t retval; int r; r = open_database_ro (db_path, &db, error); if (r != 0) return -r; retval = search_id (db, tty, error); sqlite3_close (db); return retval; } /* Reads all entries from database and calls the callback function for each entry. Returns 0 on success, -1 on failure. */ int sqlite_read_all (const char *db_path, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error) { sqlite3 *db; char *err_msg = 0; int r; r = open_database_ro (db_path, &db, error); if (r != 0) return -r; char *sql = "SELECT * FROM wtmp ORDER BY Login DESC, Logout ASC"; r = sqlite3_exec (db, sql, cb_func, userdata, &err_msg); sqlite3_close (db); if (r != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_read_all: SQL error: %s", err_msg) < 0) *error = strdup ("sqlite_read_all: Out of memory"); sqlite3_free (err_msg); return -r; } return 0; } static int export_row (sqlite3 *db_dest, sqlite3_stmt *sqlStatement, char **error) { char *endptr; const int type = sqlite3_column_int( sqlStatement, 1 ); const char *user = (const char*)sqlite3_column_text( sqlStatement, 2 ); const char *tty = (const char*)sqlite3_column_text( sqlStatement, 5 ); const char *host = (const char*)sqlite3_column_text( sqlStatement, 6 ); const char *service = (const char*)sqlite3_column_text( sqlStatement, 7 ); uint64_t login_t = strtoul((const char*)sqlite3_column_text( sqlStatement, 3 ), &endptr, 10); if ((errno == ERANGE && login_t == UINT64_MAX) || (endptr == (const char *)sqlite3_column_text( sqlStatement, 3 )) || (*endptr != '\0')) fprintf (stderr, "export_row: Invalid numeric time entry for 'login': '%s'\n", sqlite3_column_text( sqlStatement, 5 )); int64_t id = add_entry (db_dest, type, user, login_t, tty, host, service, error); if (id >=0) { const char *logout = (const char*)sqlite3_column_text( sqlStatement, 4 ); if (logout) { uint64_t logout_t = strtoul(logout, &endptr, 10); if ((errno == ERANGE && logout_t == INT64_MAX) || (endptr == logout) || (*endptr != '\0')) { fprintf (stderr, "export_row: Invalid numeric time entry for 'logout': '%s'\n", sqlite3_column_text( sqlStatement, 3 )); return -1; } if (update_logout (db_dest, id, logout_t, error) == -1) { fprintf (stderr, "export_row: Cannot update DB value: '%s'\n", *error); return -1; } } } else { fprintf (stderr, "export_row: Cannot insert DB value: '%s'\n", *error); return -1; } return 0; } /* Reads all entries from database and calls the callback function for each entry. Returns 0 on success, <0 on failure. */ int sqlite_rotate(const char *db_path, const int days, char **wtmpdb_name, uint64_t *entries, char **error) { sqlite3 *db_src; sqlite3 *db_dest; uint64_t counter = 0; struct timespec threshold; clock_gettime (CLOCK_REALTIME, &threshold); threshold.tv_sec -= days * 86400; struct tm *tm = localtime (&threshold.tv_sec); uint64_t login_t = wtmpdb_timespec2usec (threshold); char date[10]; strftime (date, 10, "%Y%m%d", tm); char *dest_path = NULL; char *dest_file = strdup(db_path); int r; strip_extension(dest_file); if (asprintf (&dest_path, "%s/%s_%s.db", dirname(dest_file), basename(dest_file), date) < 0) { *error = strdup ("sqlite_rotate: Out of memory"); return -ENOMEM; } r = open_database_rw(dest_path, &db_dest, error); if (r < 0) { free(dest_path); free(dest_file); return r; } r = open_database_rw (db_path, &db_src, error); if (r < 0) { free(dest_path); free(dest_file); sqlite3_close (db_dest); return r; } char *sql_select = "SELECT * FROM wtmp where Login <= ?"; sqlite3_stmt *res; if (sqlite3_prepare_v2 (db_src, sql_select, -1, &res, 0) != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_rotate: Failed to execute statement %s: %s", sql_select, sqlite3_errmsg (db_src)) < 0) *error = strdup ("sqlite_rotate: Out of memory"); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } if (sqlite3_bind_int64 (res, 1, login_t) != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_rotate: Failed to create replace statement for login time: %s", sqlite3_errmsg (db_src)) < 0) *error = strdup("sqlite_rotate: Out of memory"); sqlite3_finalize(res); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } int rc; while ((rc = sqlite3_step(res)) == SQLITE_ROW) { export_row (db_dest, res, error); ++counter; } if (rc != SQLITE_DONE) { if (asprintf (error, "sqlite_rotate: SQL error: %s", sqlite3_errmsg(db_src)) < 0) *error = strdup ("sqlite_rotate: Out of memory"); sqlite3_finalize(res); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } sqlite3_finalize(res); char *sql_delete = "DELETE FROM wtmp where Login <= ?"; if (sqlite3_prepare_v2 (db_src, sql_delete, -1, &res, 0) != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_rotate: Failed to execute statement %s: %s", sql_delete, sqlite3_errmsg (db_src)) < 0) *error = strdup ("sqlite_rotate: Out of memory"); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } if (sqlite3_bind_int64 (res, 1, login_t) != SQLITE_OK) { if (error) if (asprintf (error, "sqlite_rotate: Failed to create replace statement for login time: %s", sqlite3_errmsg (db_src)) < 0) *error = strdup("sqlite_rotate: Out of memory"); sqlite3_finalize(res); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } int step = sqlite3_step (res); if (step != SQLITE_DONE) { if (error) if (asprintf (error, "sqlite_rotate: Adding an entry did not return SQLITE_DONE: %d", step) < 0) *error = strdup("sqlite_rotate: Out of memory"); sqlite3_finalize(res); sqlite3_close (db_src); sqlite3_close (db_dest); free(dest_path); free(dest_file); return -1; } sqlite3_finalize(res); sqlite3_close (db_src); sqlite3_close (db_dest); if (counter > 0) { if (wtmpdb_name) *wtmpdb_name = strdup (dest_path); if (entries) *entries = counter; } else unlink (dest_path); free(dest_path); free(dest_file); return 0; } static uint64_t search_boottime (sqlite3 *db, char **error) { uint64_t boottime = 0; sqlite3_stmt *res; char *sql = "SELECT Login FROM wtmp WHERE User = 'reboot' ORDER BY Login DESC LIMIT 1;"; if (sqlite3_prepare_v2 (db, sql, -1, &res, 0) != SQLITE_OK) { if (error) if (asprintf (error, "search_boottime: Failed to execute statement: %s", sqlite3_errmsg (db)) < 0) *error = strdup ("search_boottime: Out of memory"); return 0; } int step = sqlite3_step (res); if (step == SQLITE_ROW) boottime = (uint64_t)sqlite3_column_int64 (res, 0); else { if (error) if (asprintf (error, "search_boottime: Boot time not found (%d)", step) < 0) *error = strdup("search_boottime: Out of memory"); sqlite3_finalize (res); return 0; } sqlite3_finalize (res); return boottime; } int sqlite_get_boottime (const char *db_path, uint64_t *boottime, char **error) { sqlite3 *db; int r; r = open_database_ro (db_path, &db, error); if (r != 0) return -r; *boottime = search_boottime (db, error); sqlite3_close (db); return 0; } thkukuk-wtmpdb-edb8638/lib/sqlite.h000066400000000000000000000042321477522103000173420ustar00rootroot00000000000000/* 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 sqlite_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); extern int sqlite_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error); extern int64_t sqlite_get_id (const char *db_path, const char *tty, char **error); extern int sqlite_read_all (const char *db_path, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error); extern int sqlite_get_boottime(const char *db_path, uint64_t *boottime, char **error); extern int sqlite_rotate (const char *db_path, const int days, char **wtmpdb_name, uint64_t *entries, char **error); thkukuk-wtmpdb-edb8638/lib/varlink.c000066400000000000000000000445041477522103000175100ustar00rootroot00000000000000/* 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 WITH_WTMPDBD #include #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, 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, 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, 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, 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, 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; } struct read_all { bool success; char *error; sd_json_variant *contents_json; }; static void read_all_free (struct read_all *var) { var->error = mfree(var->error); var->contents_json = sd_json_variant_unref(var->contents_json); } struct wtmpdb_entry { int64_t id; int type; char *user; uint64_t login; uint64_t logout; char *tty; char *remote_host; char *service; }; static void wtmpdb_entry_free (struct wtmpdb_entry *var) { var->user = mfree(var->user); var->tty = mfree(var->tty); var->remote_host = mfree(var->remote_host); var->service = mfree(var->service); } int varlink_read_all (int (*cb_func)(void *unused, int argc, char **argv, char **azColName), void *userdata, char **error) { _cleanup_(read_all_free) struct read_all p = { .success = false, .error = NULL, .contents_json = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct read_all, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct read_all, error), 0 }, { "Data", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(struct read_all, contents_json), 0 }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; sd_json_variant *result; int r; r = connect_to_wtmpdbd(&link, _VARLINK_WTMPDB_SOCKET, error); if (r < 0) return r; const char *error_id; r = sd_varlink_call(link, "org.openSUSE.wtmpdb.ReadAll", NULL, &result, &error_id); if (r < 0) { if (error) if (asprintf (error, "Failed to call ReadAll 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 (!sd_json_variant_is_array(p.contents_json)) { fprintf(stderr, "JSON 'Data' is no array!\n"); return -EINVAL; } for (size_t i = 0; i < sd_json_variant_elements(p.contents_json); i++) { static char *azColName[8] = {"ID", "Type", "User", "Login", "Logout", "TTY", "RemoteHost", "Service"}; _cleanup_(wtmpdb_entry_free) struct wtmpdb_entry e = { .id = -1, .type = -1, .user = NULL, .login = 0, .logout = 0, .tty = NULL, .remote_host = NULL, .service = NULL }; static const sd_json_dispatch_field dispatch_entry_table[] = { { "ID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct wtmpdb_entry, id), SD_JSON_MANDATORY }, { "Type", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct wtmpdb_entry, type), 0 }, { "User", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct wtmpdb_entry, user), SD_JSON_MANDATORY }, { "Login", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct wtmpdb_entry, login), 0 }, { "Logout", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, offsetof(struct wtmpdb_entry, logout), 0 }, { "TTY", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct wtmpdb_entry, tty), 0 }, { "RemoteHost", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct wtmpdb_entry, remote_host), 0 }, { "Service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct wtmpdb_entry, service), 0 }, {} }; sd_json_variant *entry = sd_json_variant_by_index(p.contents_json, i); if (!sd_json_variant_is_object(entry)) { fprintf(stderr, "entry is no object!\n"); return -EINVAL; } r = sd_json_dispatch(entry, dispatch_entry_table, SD_JSON_ALLOW_EXTENSIONS, &e); if (r < 0) { if (error) if (asprintf (error, "Failed to parse JSON wtmpdb entry: %s", strerror(-r)) < 0) *error = strdup("Out of memory"); return r; } char *ret[8]; if (asprintf (&ret[0], "%" PRId64, e.id) < 0) return -ENOMEM; if (asprintf (&ret[1], "%i", e.type) < 0) return -ENOMEM; ret[2] = e.user; if (asprintf (&ret[3], "%" PRIu64, e.login) < 0) return -ENOMEM; if (e.logout > 0) { if (asprintf (&ret[4], "%" PRIu64, e.logout) < 0) return -ENOMEM; } else ret[4] = NULL; ret[5] = e.tty; if (strlen(e.remote_host) > 0) ret[6] = e.remote_host; else ret[6] = NULL; if (strlen(e.service) > 0) ret[7] = e.service; else ret[7] = NULL; cb_func(userdata, 8, ret, azColName); /* XXX Don't ignore return value */ free(ret[0]); free(ret[1]); free(ret[3]); free(ret[4]); } return 0; } #endif thkukuk-wtmpdb-edb8638/lib/varlink.h000066400000000000000000000040051477522103000175050ustar00rootroot00000000000000/* 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), void *userdata, 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); thkukuk-wtmpdb-edb8638/man/000077500000000000000000000000001477522103000156745ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/man/custom-man.xsl000066400000000000000000000007001477522103000205040ustar00rootroot00000000000000 thkukuk-wtmpdb-edb8638/man/meson.build000066400000000000000000000024661477522103000200460ustar00rootroot00000000000000xsltproc_exe = find_program('xsltproc', required : get_option('man')) want_man = (get_option('man').enabled() or get_option('man').auto()) and xsltproc_exe.found() xsltproc_flags = [ '--nonet', '--xinclude', '--stringparam', 'version', '@0@'.format(meson.project_version()), '--path', '@0@:@1@'.format(meson.current_build_dir(), meson.current_source_dir())] custom_man_xsl = files('custom-man.xsl') xslt_cmd = [xsltproc_exe, '-o', '@OUTPUT0@'] + xsltproc_flags mandir8 = get_option('mandir') /'man8' if xsltproc_exe.found() custom_target('pam_wtmpdb.8', input : 'pam_wtmpdb.8.xml', output : 'pam_wtmpdb.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) custom_target('wtmpdb.8', input : 'wtmpdb.8.xml', output : 'wtmpdb.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) custom_target('wtmpdbd.8', input : 'wtmpdbd.8.xml', output : ['wtmpdbd.8', 'wtmpdbd.service.8', 'wtmpdbd.socket.8'], command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) endif thkukuk-wtmpdb-edb8638/man/pam_wtmpdb.8.xml000066400000000000000000000116741477522103000207270ustar00rootroot00000000000000 pam_wtmpdb 8 wtmpdb %version% pam_wtmpdb pam_wtmpdb PAM module to record login and logout times of users pam_wtmpdb.so debug silent skip_if=<services> database=<file> DESCRIPTION pam_wtmpdb is a PAM module to record the login and logout information of the user. The module uses /var/lib/wtmpdb/wtmp.db as database file to store all information. Compared to some wtmp5 implementations this PAM module is Y2038 safe and uses sqlite3 to store the information. OPTIONS debug Print debug information. silent Avoid all messages except errors. skip_if=<services> The argument is a comma separated list of PAM services. If a service is listed here, no wtmpdb entry is written. database=<file> Use instead of /var/lib/wtmpdb/wtmp.db. MODULE TYPES PROVIDED The module type is provided for updating the wtmp database with the login and logout information about an user. RETURN VALUES PAM_SUCCESS Everything was successful. PAM_SERVICE_ERR Internal service module error. This includes error reading from or writing to the database. PAM_USER_UNKNOWN User not known. PAM_IGNORE Returned by service types which do nothing. EXAMPLES Add the following line to e.g. /etc/pam.d/login to display the last login time of a user: session required pam_wtmpdb.so FILES /var/lib/wtmpdb/wtmp.db Wtmpdb logging database file SEE ALSO wtmpdb8 , pam.conf5 , pam.d5 , pam8 AUTHOR pam_wtmpdb was written by Thorsten Kukuk <kukuk@suse.com>. thkukuk-wtmpdb-edb8638/man/wtmpdb.8.xml000066400000000000000000000246521477522103000200720ustar00rootroot00000000000000 wtmpdb 8 wtmpdb %version% wtmpdb wtmpdb display login, logout and reboot information wtmpdb COMMAND option DESCRIPTION wtmpdb displays the content of the wtmp database and allows to create reboot and shutdown entries. This command is Y2038 safe and uses sqlite3 to store the information. COMMANDS The following commands are understood: last option… username… tty… wtmpdb last goes through the /var/lib/wtmpdb/wtmp.db database (or the database designated by the -f option) and displays a list of of all users logged in and logged out. The output can be restricted to different patterns via various options. If one or more usernames and/or ttys are given wtmpdb last will only show the entries matching those arguments. The login and logout times of the special user reboot are the boot and shutdown times of the system. Display hostnames in the last column. Translate IP addresses into a hostname. FILE Use FILE as wtmpdb database. Display full times and dates. Translate hostnames to IP addresses. N N Display only the first N entries. TIME Display who was present at TIME. Don't display any hostname or IP address. Display PAM service used to login. TIME Print only records more recent than TIME. TIME Print only records until TIME. Display full IP addresses and user and domain names. Display system shutdown entries. FORMAT Display timestamps in the specified FORMAT. The format can be notime, short, full, or iso. notime will not display times at all, short is the default option, full will display the full times and dates, and iso will display times in ISO-8601 format. TIME must be in the format , , , , , , , , or (time will be set to 00:00:00 if not specified; the date to today's). boot option… wtmpdb boot writes system boot times to the /var/lib/wtmpdb/wtmp.db database. boot options FILE Use FILE as wtmpdb database. Don't print informative messages. shutdown option… wtmpdb shutdown writes system shutdown requests to the /var/lib/wtmpdb/wtmp.db database. shutdown options FILE Use FILE as wtmpdb database. rotate option… wtmpdb rotate exports old log entries to the /var/lib/wtmpdb/wtmp_yyyymmmdd.db database and removes these entries from the original one. rotate options FILE Use FILE as wtmpdb database. The exported DB file will be on the same location. DAYS Entries will be exported which are older than DAYS days. Default is 60 days. import option… file… wtmpdb import imports legacy wtmp log files to the /var/lib/wtmpdb/wtmp.db database. import options FILE Use FILE as wtmpdb database. global options global options Display help message and exit. Print version number and exit. FILES /var/lib/wtmpdb/wtmp.db Wtmpdb logging database file SEE ALSO pam_wtmpdb8 , AUTHOR wtmpdb was written by Thorsten Kukuk <kukuk@suse.com>. thkukuk-wtmpdb-edb8638/man/wtmpdbd.8.xml000066400000000000000000000061211477522103000202250ustar00rootroot00000000000000 wtmpdbd.service 8 wtmpdb %version% wtmpdbd wtmpdbd.service wtmpdbd.socket wtmpdbd Daemon to control wtmpdb entries wtmpdbd.service /usr/libexec/wtmpdbd option DESCRIPTION wtmpdbd.service is a system service that may be used to add and read wtmpdb8 entries. It is automatically activated on request and terminates itself when unused. OPTIONS Activation through socket. Debug mode Verbose logging Display help message and exit. Print version number and exit. FILES /run/wtmpdb/socket Varlink socket for communication /var/lib/wtmpdb/wtmp.db Wtmpdb logging database file SEE ALSO wtmpdb8 , AUTHOR wtmpdb was written by Thorsten Kukuk <kukuk@suse.com>. thkukuk-wtmpdb-edb8638/meson.build000066400000000000000000000127031477522103000172660ustar00rootroot00000000000000project( 'wtmpdb', 'c', meson_version : '>= 0.61.0', default_options : [ 'prefix=/usr', 'sysconfdir=/etc', 'localstatedir=/var', 'buildtype=debugoptimized', 'default_library=shared', 'b_pie=true', 'b_lto=true', 'warning_level=2'], license : ['BSD-2-Clause'], version : '0.73.0', ) conf = configuration_data() conf.set_quoted('VERSION', meson.project_version()) conf.set_quoted('PACKAGE', meson.project_name()) cc = meson.get_compiler('c') pkg = import('pkgconfig') inc = include_directories(['include','lib']) add_project_arguments(['-D_GNU_SOURCE=1', '-DXTSTRINGDEFINES', '-D_FORTIFY_SOURCE=2', '-D_FILE_OFFSET_BITS=64', '-D_TIME_BITS=64'], language : 'c') possible_cc_flags = [ '-fstack-protector-strong', '-funwind-tables', '-fasynchronous-unwind-tables', '-fstack-clash-protection', '-Werror=return-type', '-Wbad-function-cast', '-Wcast-align', '-Wcast-qual', '-Wformat-security', '-Winline', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wnested-externs', '-Wshadow', '-Wstrict-prototypes', '-Wundef', ] add_project_arguments(cc.get_supported_arguments(possible_cc_flags), language : 'c') fs = import('fs') if get_option('split-usr') == 'auto' split_usr = not fs.is_symlink('/bin') else split_usr = get_option('split-usr') == 'true' endif rootprefixdir = get_option('rootprefix') rootprefix_default = split_usr ? '/' : '/usr' if rootprefixdir == '' rootprefixdir = rootprefix_default endif rootlibdir = get_option('rootlibdir') if rootlibdir == '' # This will be a relative path if libdir is in prefix. rootlibdir = get_option('libdir') endif if not rootlibdir.startswith('/') # If we have a relative path, add rootprefixdir to the front. rootlibdir = rootprefixdir / rootlibdir endif pamlibdir = get_option('pamlibdir') if pamlibdir == '' pamlibdir = rootlibdir / 'security' endif # Meson ignores the preceding arguments when joining paths if an absolute # component is encountered, so this should canonicalize various paths when they # are absolute or relative. prefixdir = get_option('prefix') if not prefixdir.startswith('/') error('Prefix is not absolute: "@0@"'.format(prefixdir)) endif if prefixdir != rootprefixdir and rootprefixdir != '/' and not prefixdir.strip('/').startswith(rootprefixdir.strip('/') + '/') error('Prefix is not below root prefix (now rootprefix=@0@ prefix=@1@)'.format(rootprefixdir, prefixdir)) endif libexecdir = join_paths(prefixdir, get_option('libexecdir')) systemunitdir = prefixdir / 'lib/systemd/system' tmpfilesdir = prefixdir / 'lib/tmpfiles.d' libpam = cc.find_library('pam') libsqlite3 = cc.find_library('sqlite3') libaudit = dependency('audit', required : get_option('audit')) conf.set10('HAVE_AUDIT', libaudit.found()) libsystemd = dependency('libsystemd', version: '>= 257', required : get_option('wtmpdbd')) conf.set10('WITH_WTMPDBD', libsystemd.found()) if libsystemd.found() have_systemd257 = true else # for soft-reboot older libsystemd with sd_bus is good enough have_systemd257 = false libsystemd = dependency('libsystemd', required : get_option('systemd')) endif conf.set10('HAVE_SYSTEMD', libsystemd.found()) 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) pam_wtmpdb_c = files('src/pam_wtmpdb.c') pam_wtmpdb_map = 'src/pam_wtmpdb.map' pam_wtmpdb_map_version = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), pam_wtmpdb_map) libwtmpdb = shared_library( 'wtmpdb', libwtmpdb_c, include_directories : inc, link_args : ['-shared', libwtmpdb_map_version], link_depends : libwtmpdb_map, dependencies : [libsqlite3, libsystemd], install : true, version : meson.project_version(), soversion : '0' ) install_headers('include/wtmpdb.h') pkg.generate( libwtmpdb, name : 'libwtmpdb', description : 'library to record all logins and logouts', version : meson.project_version(), ) pam_wtmpdb = shared_library( 'pam_wtmpdb', pam_wtmpdb_c, name_prefix : '', include_directories : inc, link_args : ['-shared', pam_wtmpdb_map_version], link_depends : pam_wtmpdb_map, link_with : libwtmpdb, dependencies : [libpam], install : true, install_dir : pamlibdir ) wtmpdb_c = ['src/wtmpdb.c', 'src/import.c'] wtmpdbd_c = ['src/wtmpdbd.c', 'src/varlink-org.openSUSE.wtmpdb.c', 'lib/mkdir_p.c'] if have_systemd257 executable('wtmpdbd', wtmpdbd_c, include_directories : inc, link_with : libwtmpdb, dependencies : [libsystemd], install_dir : libexecdir, install : true) endif executable('wtmpdb', wtmpdb_c, include_directories : inc, link_with : libwtmpdb, dependencies : [libaudit, libsystemd], install : true) if get_option('compat-symlink') install_symlink('last', pointing_to: 'wtmpdb', install_dir: 'bin') endif subdir('tmpfiles.d') subdir('units') # Unit tests subdir('tests') # Manual pages subdir('man') config_h = configure_file( output : 'config.h', configuration : conf) thkukuk-wtmpdb-edb8638/meson_options.txt000066400000000000000000000017221477522103000205600ustar00rootroot00000000000000option('wtmpdbd', type : 'feature', value : 'auto', description : 'build daemon with sd-varlink support') option('split-usr', type : 'combo', choices : ['auto', 'true', 'false'], description : '''/bin, /sbin aren't symlinks into /usr''') option('rootprefix', type : 'string', description : '''override the root prefix [default '/' if split-usr and '/usr' otherwise]''') option('rootlibdir', type : 'string', description : '''[/usr]/lib or such''') option('pamlibdir', type : 'string', description : 'directory for PAM modules') option('man', type : 'feature', value : 'auto', description : 'build and install man pages') option('audit', type : 'feature', value : 'auto', description : 'libaudit support') option('systemd', type : 'feature', value : 'auto', description : 'systemd support to detect soft-reboots') option('compat-symlink', type : 'boolean', value : false, description : 'create last compat symlink') thkukuk-wtmpdb-edb8638/src/000077500000000000000000000000001477522103000157105ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/src/import.c000066400000000000000000000122121477522103000173640ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2025, Andrew Bower 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" #include #include #include #include #include #include #include #include #include #include enum utmp_type { UTMP_EMPTY = EMPTY, UTMP_RUN_LVL = RUN_LVL, UTMP_BOOT_TIME = BOOT_TIME, UTMP_USER_PROCESS = USER_PROCESS, UTMP_DEAD_PROCESS = DEAD_PROCESS, }; #undef EMPTY #undef RUN_LVL #undef BOOT_TIME #undef USER_PROCESS #include "wtmpdb.h" #include "import.h" /* Import utmp entries from memory into a wtmpdb-format database. Returns 0 on success, -1 on failure. */ static int import_utmp_records (const char *db_path, const struct utmp *utmp_data, int entries, char **error) { int64_t last_reboot_id = -1; int64_t *id_map; int row = 0; int ret = 0; id_map = calloc (entries, sizeof *id_map); if (id_map == NULL) { *error = strdup ("wtmpdb_import: out of memory allocating id map"); return -1; } for (row = 0; ret == 0 && row < entries; row++) { const struct utmp *u = utmp_data + row; const struct utmp *v; int64_t id = -1; int64_t usecs = USEC_PER_SEC * u->ut_tv.tv_sec + u->ut_tv.tv_usec; switch (u->ut_type) { case UTMP_RUN_LVL: case UTMP_BOOT_TIME: if (u->ut_id[0] == '~' && u->ut_id[1] == '~' && u->ut_id[2] == '\0') { if (strcmp (u->ut_user, "reboot") == 0) { id = wtmpdb_login (db_path, BOOT_TIME, "reboot", usecs, "~", u->ut_host, NULL, error); ret = id == -1 ? -1 : 0; last_reboot_id = id; } else if (strcmp (u->ut_user, "shutdown") == 0 && last_reboot_id != -1) { ret = wtmpdb_logout (db_path, last_reboot_id, usecs, error); last_reboot_id = -1; } } break; case UTMP_USER_PROCESS: id = wtmpdb_login (db_path, USER_PROCESS, u->ut_user, usecs, u->ut_line, u->ut_host, NULL, error); ret = id == -1 ? -1 : 0; break; case UTMP_DEAD_PROCESS: for (v = u - 1; v >= utmp_data && v->ut_type != UTMP_BOOT_TIME; v--) { if (v->ut_type == UTMP_USER_PROCESS && ((u->ut_pid != 0 && v->ut_pid == u->ut_pid) || (u->ut_pid == 0 && strncmp (v->ut_line, u->ut_line, UT_LINESIZE) == 0))) { id = id_map[v - utmp_data]; if (id > 0) ret = wtmpdb_logout (db_path, id, usecs, error); break; } } break; } id_map[row] = id; } free (id_map); return ret; } /* Import a wtmp log file into a wtmpdb-format database. Returns 0 on success, -1 on failure. */ int import_wtmp_file (const char *db_path, const char *file) { struct stat statbuf; char *error = NULL; const ssize_t record_sz = sizeof(struct utmp); ssize_t file_sz; ssize_t entries; void *data; int fd; int rc; fd = open (file, O_RDONLY); if (fd == -1) { fprintf (stderr, "Couldn't open '%s' to import: %s\n", file, strerror (errno)); return -1; } rc = fstat (fd, &statbuf); if (rc == -1) { fprintf (stderr, "Could not stat '%s': %s\n", file, strerror (errno)); close (fd); return -1; } file_sz = statbuf.st_size; entries = file_sz / record_sz; if (entries * record_sz != file_sz) { fprintf (stderr, "Warning: utmp-format file is not a multiple of " "sizeof(struct utmp) in length: %zd spare bytes, %s\n", file_sz - entries * record_sz, file); } data = mmap (NULL, file_sz, PROT_READ, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf (stderr, "Could not map file to import: %s\n", strerror (errno)); close (fd); return -1; } rc = import_utmp_records (db_path, data, entries, &error); if (rc == -1) { fprintf (stderr, "Error importing %s: %sn", file, error); free(error); } munmap (data, file_sz); close (fd); return rc; } thkukuk-wtmpdb-edb8638/src/import.h000066400000000000000000000026651477522103000174040ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2025, Andrew Bower 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 int import_wtmp_file (const char *db_path, const char *file); thkukuk-wtmpdb-edb8638/src/pam_wtmpdb.c000066400000000000000000000221561477522103000202140ustar00rootroot00000000000000/* 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. */ #include #include #include #include #include #include #include #include #include "wtmpdb.h" #define WTMPDB_DEBUG 01 /* send info to syslog(3) */ #define WTMPDB_QUIET 02 /* keep quiet about things */ #define WTMPDB_SKIP 04 /* Skip if service is in skip list */ static const char *wtmpdb_path = _PATH_WTMPDB; /* From pam_inline.h * * Returns NULL if STR does not start with PREFIX, * or a pointer to the first char in STR after PREFIX. */ static inline const char * skip_prefix (const char *str, const char *prefix) { size_t prefix_len = strlen (prefix); return strncmp(str, prefix, prefix_len) ? NULL : str + prefix_len; } /* check for list match. */ static int check_in_list (const char *service, const char *arg) { const char *item; const char *remaining; if (!service) return 0; remaining = arg; for (;;) { item = strstr (remaining, service); if (item == NULL) break; /* is it really the start of an item in the list? */ if (item == arg || *(item - 1) == ',') { item += strlen (service); /* is item really the service? */ if (*item == '\0' || *item == ',') return 1; } remaining = strchr (item, ','); if (remaining == NULL) break; /* skip ',' */ ++remaining; } return 0; } static char tty_buf[12]; static const char * get_tty (pam_handle_t *pamh, int ctrl) { const void *void_str = NULL; const char *tty; const char *xdg_vtnr; int retval = pam_get_item (pamh, PAM_TTY, &void_str); if (retval != PAM_SUCCESS || void_str == NULL) tty = ""; else tty = void_str; /* if PAM_TTY is not set or an X11 $DISPLAY, try XDG_VTNR */ if ((tty[0] == '\0' || strchr(tty, ':') != NULL) && (xdg_vtnr = pam_getenv (pamh, "XDG_VTNR")) != NULL) { int xdg_vtnr_nr = atoi (xdg_vtnr); if (xdg_vtnr_nr > 0 && snprintf (tty_buf, sizeof(tty_buf), "tty%d", xdg_vtnr_nr) < (int) sizeof(tty_buf)) { tty = tty_buf; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "tty(XDG_VTNR)=%s", tty); } } /* strip leading "/dev/" from tty. */ const char *str = skip_prefix(tty, "/dev/"); if (str != NULL) tty = str; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "tty=%s", tty); return tty; } static int _pam_parse_args (pam_handle_t *pamh, int flags, int argc, const char **argv) { int ctrl = 0; const char *str; /* does the application require quiet? */ if (flags & PAM_SILENT) ctrl |= WTMPDB_QUIET; /* step through arguments */ for (; argc-- > 0; ++argv) { if (strcmp (*argv, "debug") == 0) ctrl |= WTMPDB_DEBUG; else if (strcmp (*argv, "silent") == 0) ctrl |= WTMPDB_QUIET; else if ((str = skip_prefix(*argv, "database=")) != NULL) wtmpdb_path = str; else if ((str = skip_prefix (*argv, "skip_if=")) != NULL) { const void *void_str = NULL; const char *service; if ((pam_get_item (pamh, PAM_SERVICE, &void_str) != PAM_SUCCESS) || void_str == NULL) service = ""; else service = void_str; if (check_in_list (service, str)) { if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "skip_if='%s' contains '%s'", str, service); ctrl |= WTMPDB_SKIP; } } else pam_syslog (pamh, LOG_ERR, "Unknown option: %s", *argv); } return ctrl; } int pam_sm_authenticate (pam_handle_t *pamh __attribute__((__unused__)), int flags __attribute__((__unused__)), int argc __attribute__((__unused__)), const char **argv __attribute__((__unused__))) { return PAM_IGNORE; } int pam_sm_setcred (pam_handle_t *pamh __attribute__((__unused__)), int flags __attribute__((__unused__)), int argc __attribute__((__unused__)), const char **argv __attribute__((__unused__))) { return PAM_IGNORE; } int pam_sm_acct_mgmt (pam_handle_t *pamh __attribute__((__unused__)), int flags __attribute__((__unused__)), int argc __attribute__((__unused__)), const char **argv __attribute__((__unused__))) { return PAM_IGNORE; } static void free_idptr(pam_handle_t *pamh __attribute__((__unused__)), void *idptr, int error_status __attribute__((__unused__))) { free (idptr); } int pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char **argv) { const struct passwd *pwd; const void *void_str; const char *user; const char *tty; const char *rhost; const char *service; char *error = NULL; int ctrl; int64_t id; ctrl = _pam_parse_args (pamh, flags, argc, argv); if (ctrl & WTMPDB_SKIP) return PAM_IGNORE; void_str = NULL; int retval = pam_get_item (pamh, PAM_USER, &void_str); if (retval != PAM_SUCCESS || void_str == NULL || strlen (void_str) == 0) { if (!(ctrl & WTMPDB_QUIET)) pam_syslog (pamh, LOG_NOTICE, "User unknown"); return PAM_USER_UNKNOWN; } user = void_str; /* verify the user exists */ pwd = pam_modutil_getpwnam (pamh, user); if (pwd == NULL) { if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "Couldn't find user %s", (const char *)user); return PAM_USER_UNKNOWN; } if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "user=%s", user); tty = get_tty (pamh, ctrl); void_str = NULL; retval = pam_get_item (pamh, PAM_RHOST, &void_str); if (retval != PAM_SUCCESS || void_str == NULL) { void_str = NULL; retval = pam_get_item (pamh, PAM_XDISPLAY, &void_str); if (retval != PAM_SUCCESS || void_str == NULL) rhost = ""; else { rhost = void_str; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_XDISPLAY)=%s", rhost); } } else { rhost = void_str; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_RHOST)=%s", rhost); } void_str = NULL; retval = pam_get_item (pamh, PAM_SERVICE, &void_str); if (retval != PAM_SUCCESS || void_str == NULL) service = ""; else service = void_str; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "service=%s", service); if ((id = logwtmpdb (wtmpdb_path, tty, user, rhost, service, &error)) < 0) { if (error) { pam_syslog (pamh, LOG_ERR, "%s", error); free (error); } else pam_syslog (pamh, LOG_ERR, "Unknown error writing to database %s", wtmpdb_path); return PAM_SYSTEM_ERR; } if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "id=%lld", (long long int)id); int64_t *idptr = calloc (1, sizeof(int64_t)); *idptr = id; pam_set_data(pamh, "ID", idptr, free_idptr); return PAM_SUCCESS; } int pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv) { char *error = NULL; int ctrl = _pam_parse_args (pamh, flags, argc, argv); const void *voidptr = NULL; const int64_t *idptr; int retval; struct timespec ts; if (ctrl & WTMPDB_SKIP) return PAM_IGNORE; clock_gettime (CLOCK_REALTIME, &ts); if ((retval = pam_get_data (pamh, "ID", &voidptr)) != PAM_SUCCESS) { pam_syslog (pamh, LOG_ERR, "Cannot get ID from open session!"); return retval; } idptr = voidptr; int64_t id = *idptr; if (ctrl & WTMPDB_DEBUG) pam_syslog (pamh, LOG_DEBUG, "id=%lli", (long long int)id); if (wtmpdb_logout (wtmpdb_path, id, wtmpdb_timespec2usec (ts), &error) < 0) { if (error) { pam_syslog (pamh, LOG_ERR, "%s", error); free (error); } else pam_syslog (pamh, LOG_ERR, "Unknown error writing logout time to database %s", wtmpdb_path); return PAM_SYSTEM_ERR; } return PAM_SUCCESS; } thkukuk-wtmpdb-edb8638/src/pam_wtmpdb.map000066400000000000000000000002471477522103000205440ustar00rootroot00000000000000{ global: pam_sm_acct_mgmt; pam_sm_authenticate; pam_sm_chauthtok; pam_sm_close_session; pam_sm_open_session; pam_sm_setcred; local: *; }; thkukuk-wtmpdb-edb8638/src/varlink-org.openSUSE.wtmpdb.c000066400000000000000000000125621477522103000232510ustar00rootroot00000000000000//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, 0), SD_VARLINK_DEFINE_FIELD(User, SD_VARLINK_STRING, 0), 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, 0), 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_METHOD( Ping, SD_VARLINK_FIELD_COMMENT("Check if service is alive"), SD_VARLINK_DEFINE_OUTPUT(Alive, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( SetLogLevel, SD_VARLINK_FIELD_COMMENT("The maximum log level, using BSD syslog log level integers."), SD_VARLINK_DEFINE_INPUT(Level, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetEnvironment, SD_VARLINK_FIELD_COMMENT("Returns the current environment block, i.e. the contents of environ[]."), SD_VARLINK_DEFINE_OUTPUT(Environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); 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("Checks if the service is running."), &vl_method_Ping, SD_VARLINK_SYMBOL_COMMENT("Sets the maximum log level."), &vl_method_SetLogLevel, SD_VARLINK_SYMBOL_COMMENT("Get current environment block."), &vl_method_GetEnvironment, SD_VARLINK_SYMBOL_COMMENT("No entry found"), &vl_error_NoEntryFound, SD_VARLINK_SYMBOL_COMMENT("Internal Error"), &vl_error_InternalError); thkukuk-wtmpdb-edb8638/src/varlink-org.openSUSE.wtmpdb.h000066400000000000000000000002441477522103000232500ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include extern const sd_varlink_interface vl_interface_org_openSUSE_wtmpdb; thkukuk-wtmpdb-edb8638/src/wtmpdb.c000066400000000000000000000771631477522103000173670ustar00rootroot00000000000000/* 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. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_AUDIT #include #endif #if HAVE_SYSTEMD #include #define _cleanup_(f) __attribute__((cleanup(f))) #endif #include "import.h" #include "wtmpdb.h" static char *wtmpdb_path = NULL; #define TIMEFMT_CTIME 1 #define TIMEFMT_SHORT 2 #define TIMEFMT_HHMM 3 #define TIMEFMT_NOTIME 4 #define TIMEFMT_ISO 5 #define TIMEFMT_VALUE 255 #define LOGROTATE_DAYS 60 /* lenght of login string cannot become longer */ #define LAST_TIMESTAMP_LEN 32 static uint64_t wtmp_start = UINT64_MAX; static int after_reboot = 0; /* options for last */ static int hostlast = 0; static int nohostname = 0; static int noservice = 1; static int dflag = 0; static int iflag = 0; static int jflag = 0; static int wflag = 0; static int xflag = 0; static const int name_len = 8; /* LAST_LOGIN_LEN */ static int login_fmt = TIMEFMT_SHORT; static int login_len = 16; /* 16 = short, 24 = full */ static int logout_fmt = TIMEFMT_HHMM; static int logout_len = 5; /* 5 = short, 24 = full */ static const int host_len = 16; /* LAST_DOMAIN_LEN */ static unsigned long maxentries = 0; /* max number of entries to show */ static unsigned long currentry = 0; /* number of entries already printed */ static time_t present = 0; /* Who was present at the specified time */ static time_t since = 0; /* Who was logged in after this time? */ static time_t until = 0; /* Who was logged in until this time? */ static char **match = NULL; /* user/tty to display only */ /* isipaddr - find out if string provided is an IP address or not 0 - no IP address 1 - is IP address */ static int isipaddr (const char *string, int *addr_type, struct sockaddr_storage *addr) { struct sockaddr_storage local_addr; int is_ip; if (addr == NULL) addr = &local_addr; memset(addr, 0, sizeof (struct sockaddr_storage)); /* first ipv4 */ if (inet_pton (AF_INET, string, &((struct sockaddr_in *)addr)->sin_addr) > 0) { if (addr_type != NULL) *addr_type = AF_INET; addr->ss_family = AF_INET; is_ip = 1; } else if (inet_pton (AF_INET6, string, &((struct sockaddr_in6 *)addr)->sin6_addr) > 0) { /* then ipv6 */ if (addr_type != NULL) *addr_type = AF_INET6; addr->ss_family = AF_INET6; is_ip = 1; } else is_ip = 0; return is_ip; } static int parse_time (const char *str, time_t *arg) { const char *abs_datetime_fmts[] = { "%Y%m%d%H%M%S", "%Y-%m-%d %T", "%Y-%m-%d %R", "%Y-%m-%d", NULL }; const char *abs_time_fmts[] = { "%T", "%R", NULL }; const char **fmt; struct tm rst = { .tm_isdst = -1 }; struct tm res; char *r = NULL; time_t t = time (NULL); for (fmt = abs_datetime_fmts; *fmt; fmt++) { res = rst; r = strptime (str, *fmt, &res); if (r != NULL && *r == '\0') goto done; } /* Use today's date for time-only specs */ localtime_r (&t, &rst); rst.tm_isdst = -1; for (fmt = abs_time_fmts; *fmt; fmt++) { res = rst; r = strptime (str, *fmt, &res); if (r != NULL && *r == '\0') goto done; } if (strcmp (str, "now") == 0) goto done; /* Reset time of day for date-only specs */ res.tm_sec = res.tm_min = res.tm_hour = 0; if (strcmp (str, "yesterday") == 0) res.tm_mday--; else if (strcmp (str, "tomorrow") == 0) res.tm_mday++; else if (strcmp (str, "today") != 0) return -1; done: *arg = mktime (&res); return 0; } static int time_format (const char *fmt) { if (strcmp (fmt, "notime") == 0) { login_fmt = TIMEFMT_NOTIME; login_len = 0; logout_fmt = TIMEFMT_NOTIME; logout_len = 0; return TIMEFMT_NOTIME; } if (strcmp (fmt, "short") == 0) { login_fmt = TIMEFMT_SHORT; login_len = 16; logout_fmt = TIMEFMT_HHMM; logout_len = 5; return TIMEFMT_SHORT; } if (strcmp (fmt, "full") == 0) { login_fmt = TIMEFMT_CTIME; login_len = 24; logout_fmt = TIMEFMT_CTIME; logout_len = 24; return TIMEFMT_CTIME; } if (strcmp (fmt, "iso") == 0) { login_fmt = TIMEFMT_ISO; login_len = 25; logout_fmt = TIMEFMT_ISO; logout_len = 25; return TIMEFMT_ISO; } return -1; } static void format_time (int fmt, char *dst, size_t dstlen, uint64_t time) { switch (fmt) { case TIMEFMT_CTIME: { time_t t = (time_t)time; snprintf (dst, dstlen, "%s", ctime (&t)); dst[strlen (dst)-1] = '\0'; /* Remove trailing '\n' */ break; } case TIMEFMT_SHORT: { time_t t = (time_t)time; struct tm *tm = localtime (&t); strftime (dst, dstlen, "%a %b %e %H:%M", tm); break; } case TIMEFMT_HHMM: { time_t t = (time_t)time; struct tm *tm = localtime (&t); strftime (dst, dstlen, "%H:%M", tm); break; } case TIMEFMT_ISO: { time_t t = (time_t)time; struct tm *tm = localtime (&t); strftime (dst, dstlen, "%FT%T%z", tm); /* Same ISO8601 format original last command uses */ break; } case TIMEFMT_NOTIME: *dst = '\0'; break; default: abort (); } } static void calc_time_length(char *dst, size_t dstlen, uint64_t start, uint64_t stop) { uint64_t secs = (stop - start)/USEC_PER_SEC; int mins = (secs / 60) % 60; int hours = (secs / 3600) % 24; uint64_t days = secs / 86400; if (days) snprintf (dst, dstlen, "(%" PRId64 "+%02d:%02d)", days, hours, mins); else if (hours) snprintf (dst, dstlen, " (%02d:%02d)", hours, mins); else snprintf (dst, dstlen, " (00:%02d)", mins); } /* map "soft-reboot" to "s-reboot" if we have only 8 characters for user output (no -w specified) */ static const char * map_soft_reboot (const char *user) { if (wflag || strcmp (user, "soft-reboot") != 0) return user; if ((int)strlen (user) > name_len) return "s-reboot"; return user; } static const char * remove_parentheses(const char *str) { static char buf[LAST_TIMESTAMP_LEN]; if (strlen(str) >= LAST_TIMESTAMP_LEN) return str; char *cp = strchr (str, '('); if (cp == NULL) return str; cp++; strncpy(buf, cp, LAST_TIMESTAMP_LEN); cp = strchr (buf, ')'); if (cp) *cp = '\0'; return buf; } static int first_entry = 1; static void print_line (const char *user, const char *tty, const char *host, const char *print_service, const char *logintime, const char *logouttime, const char *length) { if (jflag) { if (first_entry) first_entry = 0; else printf (",\n"); printf (" { \"user\": \"%s\",\n", user); printf (" \"tty\": \"%s\",\n", tty); if (!nohostname) printf (" \"hostname\": \"%s\",\n", host); if (print_service && strlen (print_service) > 0) printf (" \"service\": \"%s\",\n", print_service); printf (" \"login\": \"%s\",\n", logintime); if (length[0] == ' ' || length[0] == '(') { printf (" \"logout\": \"%s\",\n", logouttime); printf (" \"length\": \"%s\"\n", remove_parentheses(length)); } else printf (" \"logout\": \"%s %s\"\n", logouttime, length); printf (" }"); } else { char *line; if (nohostname) { if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s - %-*.*s %s\n", wflag?(int)strlen (user):name_len, map_soft_reboot (user), tty, print_service, login_len, login_len, logintime, logout_len, logout_len, logouttime, length) < 0) { fprintf (stderr, "Out f memory"); exit (EXIT_FAILURE); } } else { if (hostlast) { if (asprintf (&line, "%-8.*s %-12.12s%s %-*.*s - %-*.*s %-12.12s %s\n", wflag?(int)strlen(user):name_len, map_soft_reboot (user), tty, print_service, login_len, login_len, logintime, logout_len, logout_len, logouttime, length, host) < 0) { fprintf (stderr, "Out f memory"); exit (EXIT_FAILURE); } } else { if (asprintf (&line, "%-8.*s %-12.12s %-16.*s%s %-*.*s - %-*.*s %s\n", wflag?(int)strlen(user):name_len, map_soft_reboot (user), tty, wflag?(int)strlen(host):host_len, host, print_service, login_len, login_len, logintime, logout_len, logout_len, logouttime, length) < 0) { fprintf (stderr, "Out f memory"); exit (EXIT_FAILURE); } } } printf ("%s", line); free (line); } } static int print_entry (void *unused __attribute__((__unused__)), int argc, char **argv, char **azColName) { char host_buf[NI_MAXHOST]; char logintime[32]; /* LAST_TIMESTAMP_LEN */ char logouttime[32]; /* LAST_TIMESTAMP_LEN */ char length[32]; /* LAST_TIMESTAMP_LEN */ char *endptr; uint64_t logout_t = 0; static uint64_t newer_boot = 0; /* Yes, it's waste of time to let sqlite iterate through all entries even if we don't need more anymore, but telling sqlite we don't want more leads to a "query aborted" error... */ if (maxentries && currentry >= maxentries) return 0; /* ID, Type, User, LoginTime, LogoutTime, TTY, RemoteHost, Service */ if (argc != 8) { fprintf (stderr, "Mangled entry:"); for (int i = 0; i < argc; i++) fprintf (stderr, " %s=%s", azColName[i], argv[i] ? argv[i] : "NULL"); fprintf (stderr, "\n"); exit (EXIT_FAILURE); } 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')) fprintf (stderr, "Invalid numeric time entry for 'login': '%s'\n", argv[3]); if (login_t < wtmp_start) wtmp_start = login_t; if (since && (since > (time_t)(login_t/USEC_PER_SEC))) return 0; if (until && (until < (time_t)(login_t/USEC_PER_SEC))) return 0; if (present && (present < (time_t)(login_t/USEC_PER_SEC))) return 0; if (match) { char **walk; for (walk = match; *walk; walk++) { if (strcmp (user, *walk) == 0 || strcmp(tty, *walk) == 0) break; } if (*walk == NULL) return 0; } format_time (login_fmt, logintime, sizeof (logintime), login_t/USEC_PER_SEC); if (argv[4]) { logout_t = strtoull(argv[4], &endptr, 10); if ((errno == ERANGE && logout_t == ULLONG_MAX) || (endptr == argv[4]) || (*endptr != '\0')) fprintf (stderr, "Invalid numeric time entry for 'logout': '%s'\n", argv[4]); if (present && (0 < (logout_t/USEC_PER_SEC)) && ((time_t)(logout_t/USEC_PER_SEC) < present)) return 0; format_time (logout_fmt, logouttime, sizeof (logouttime), logout_t/USEC_PER_SEC); calc_time_length (length, sizeof(length), login_t, logout_t); } else /* login but no logout */ { if (after_reboot) { snprintf (logouttime, sizeof (logouttime), "crash"); length[0] = '\0'; } else { switch (type) { case USER_PROCESS: if (logout_fmt == TIMEFMT_HHMM) { snprintf (logouttime, sizeof (logouttime), "still"); snprintf(length, sizeof(length), "logged in"); } else { snprintf (logouttime, sizeof (logouttime), "still logged in"); length[0] = '\0'; } break; case BOOT_TIME: if (logout_fmt == TIMEFMT_HHMM) { snprintf (logouttime, sizeof (logouttime), "still"); snprintf(length, sizeof(length), "running"); } else { snprintf (logouttime, sizeof (logouttime), "still running"); length[0] = '\0'; } break; default: snprintf (logouttime, sizeof (logouttime), "ERROR"); snprintf(length, sizeof(length), "Unknown: %d", type); break; } } } if (type == BOOT_TIME) { tty = "system boot"; after_reboot = 1; } char *print_service = NULL; if (noservice) print_service = strdup (""); else { if (asprintf (&print_service, " %-12.12s", service) < 0) { fprintf (stderr, "Out f memory"); exit (EXIT_FAILURE); } } if (dflag && strlen (host) > 0) { struct sockaddr_storage addr; int addr_type = 0; if (isipaddr (host, &addr_type, &addr)) { if (getnameinfo ((struct sockaddr*)&addr, sizeof (addr), host_buf, sizeof (host_buf), NULL, 0, NI_NAMEREQD) == 0) host = host_buf; } } if (iflag && strlen (host) > 0) { struct addrinfo hints; struct addrinfo *result; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ if (getaddrinfo(host, NULL, &hints, &result) == 0) { if (result->ai_family == AF_INET) { if (inet_ntop(result->ai_family, &((struct sockaddr_in *)result->ai_addr)->sin_addr, host_buf, sizeof (host_buf)) != NULL) host = host_buf; } else if (result->ai_family == AF_INET6) { if (inet_ntop(result->ai_family, &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, host_buf, sizeof (host_buf)) != NULL) host = host_buf; } freeaddrinfo(result); } } print_line (user, tty, host, print_service, logintime, logouttime, length); if (xflag && (type == BOOT_TIME) && newer_boot != 0 && logout_t != 0) { format_time (login_fmt, logintime, sizeof (logintime), logout_t/USEC_PER_SEC); format_time (logout_fmt, logouttime, sizeof (logouttime), newer_boot/USEC_PER_SEC); calc_time_length (length, sizeof(length), logout_t, newer_boot); print_line ("shutdown", "system down", host, print_service, logintime, logouttime, length); } if (xflag && (type == BOOT_TIME)) newer_boot = login_t; free (print_service); currentry++; return 0; } static void usage (int retval) { FILE *output = (retval != EXIT_SUCCESS) ? stderr : stdout; fprintf (output, "Usage: wtmpdb [command] [options]\n"); fputs ("Commands: last, boot, boottime, rotate, shutdown, import\n\n", output); fputs ("Options for last:\n", output); fputs (" -a, --hostlast Display hostnames as last entry\n", output); fputs (" -d, --dns Translate IP addresses into a hostname\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs (" -F, --fulltimes Display full times and dates\n", output); fputs (" -i, --ip Translate hostnames to IP addresses\n", output); fputs (" -j, --json Generate JSON output\n", output); fputs (" -n, --limit N, -N Display only first N entries\n", output); fputs (" -p, --present TIME Display who was present at TIME\n", output); fputs (" -R, --nohostname Don't display hostname\n", output); fputs (" -S, --service Display PAM service used to login\n", output); fputs (" -s, --since TIME Display who was logged in after TIME\n", output); fputs (" -t, --until TIME Display who was logged in until TIME\n", output); fputs (" -w, --fullnames Display full IP addresses and user and domain names\n", output); fputs (" -x, --system Display system shutdown entries\n", output); fputs (" --time-format FORMAT Display timestamps in the specified FORMAT:\n", output); fputs (" notime|short|full|iso\n", output); fputs (" [username...] Display only entries matching these arguments\n", output); fputs (" [tty...] Display only entries matching these arguments\n", output); fputs ("TIME must be in the format \"YYYY-MM-DD HH:MM:SS\"\n", output); fputs ("\n", output); fputs ("Options for boot (writes boot entry to wtmpdb):\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs ("\n", output); fputs ("Options for boottime (print time of last system boot):\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs ("\n", output); fputs ("Options for rotate (exports old entries to wtmpdb_)):\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs (" -d, --days INTEGER Export all entries which are older than the given days\n", output); fputs ("\n", output); fputs ("Options for shutdown (writes shutdown time to wtmpdb):\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs ("\n", output); fputs ("Options for import (imports legacy wtmp logs):\n", output); fputs (" -f, --file FILE Use FILE as wtmpdb database\n", output); fputs (" logs... Legacy log files to import\n", output); fputs ("\n", output); fputs ("Generic options:\n", output); fputs (" -h, --help Display this help message and exit\n", output); fputs (" -v, --version Print version number and exit\n", output); fputs ("\n", output); exit (retval); } static int main_rotate (int argc, char **argv) { struct option const longopts[] = { {"file", required_argument, NULL, 'f'}, {"days", no_argument, NULL, 'd'}, {NULL, 0, NULL, '\0'} }; char *error = NULL; int days = LOGROTATE_DAYS; char *wtmpdb_backup = NULL; uint64_t entries = 0; int c; while ((c = getopt_long (argc, argv, "f:d:", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; case 'd': days = atoi (optarg); break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); usage (EXIT_FAILURE); } if (wtmpdb_rotate (wtmpdb_path, days, &error, &wtmpdb_backup, &entries) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "Couldn't read all wtmp entries\n"); exit (EXIT_FAILURE); } if (entries == 0 || wtmpdb_backup == NULL) printf ("No old entries found\n"); else printf ("%lli entries moved to %s\n", (long long unsigned int)entries, wtmpdb_backup); free (wtmpdb_backup); return EXIT_SUCCESS; } static int main_last (int argc, char **argv) { struct option const longopts[] = { {"hostlast", no_argument, NULL, 'a'}, {"dns", no_argument, NULL, 'd'}, {"file", required_argument, NULL, 'f'}, {"fullnames", no_argument, NULL, 'w'}, {"fulltimes", no_argument, NULL, 'F'}, {"ip", no_argument, NULL, 'i'}, {"limit", required_argument, NULL, 'n'}, {"present", required_argument, NULL, 'p'}, {"nohostname", no_argument, NULL, 'R'}, {"service", no_argument, NULL, 'S'}, {"since", required_argument, NULL, 's'}, {"system", no_argument, NULL, 'x'}, {"until", required_argument, NULL, 't'}, {"time-format", required_argument, NULL, TIMEFMT_VALUE}, {"json", no_argument, NULL, 'j'}, {NULL, 0, NULL, '\0'} }; int time_fmt = TIMEFMT_CTIME; char *error = NULL; int c; while ((c = getopt_long (argc, argv, "0123456789adf:Fijn:p:RSs:t:wx", longopts, NULL)) != -1) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': maxentries = maxentries * 10 + c - '0'; break; case 'a': hostlast = 1; break; case 'd': dflag = 1; break; case 'f': wtmpdb_path = optarg; break; case 'F': login_fmt = TIMEFMT_CTIME; login_len = 24; logout_fmt = TIMEFMT_CTIME; logout_len = 24; break; case 'i': iflag = 1; break; case 'j': jflag = 1; break; case 'n': maxentries = strtoul (optarg, NULL, 10); break; case 'p': if (parse_time (optarg, &present) < 0) { fprintf (stderr, "Invalid time value '%s'\n", optarg); exit (EXIT_FAILURE); } break; case 'R': nohostname = 1; break; case 's': if (parse_time (optarg, &since) < 0) { fprintf (stderr, "Invalid time value '%s'\n", optarg); exit (EXIT_FAILURE); } break; case 'S': noservice = 0; break; case 't': if (parse_time (optarg, &until) < 0) { fprintf (stderr, "Invalid time value '%s'\n", optarg); exit (EXIT_FAILURE); } break; case 'w': wflag = 1; break; case 'x': xflag = 1; break; case TIMEFMT_VALUE: time_fmt = time_format (optarg); if (time_fmt == -1) { fprintf (stderr, "Invalid time format '%s'\n", optarg); exit (EXIT_FAILURE); } break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) match = argv + optind; if (nohostname && hostlast) { fprintf (stderr, "The options -a and -R cannot be used together.\n"); usage (EXIT_FAILURE); } if (nohostname && dflag) { fprintf (stderr, "The options -d and -R cannot be used together.\n"); usage (EXIT_FAILURE); } if (nohostname && iflag) { fprintf (stderr, "The options -i and -R cannot be used together.\n"); usage (EXIT_FAILURE); } if (dflag && iflag) { fprintf (stderr, "The options -d and -i cannot be used together.\n"); usage (EXIT_FAILURE); } if (jflag) printf ("{\n \"entries\": [\n"); if (wtmpdb_read_all (wtmpdb_path, print_entry, &error) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "Couldn't read all wtmp entries\n"); exit (EXIT_FAILURE); } if (wtmp_start == UINT64_MAX) { if (!jflag) printf ("%s has no entries\n", wtmpdb_path?wtmpdb_path:"wtmpdb"); } else if (time_fmt != TIMEFMT_NOTIME) { char wtmptime[32]; format_time (time_fmt, wtmptime, sizeof (wtmptime), wtmp_start/USEC_PER_SEC); if (jflag) printf ("\n ],\n \"start\": \"%s\"\n", wtmptime); else printf ("\n%s begins %s\n", wtmpdb_path?wtmpdb_path:"wtmpdb", wtmptime); } else if (jflag) printf ("\n ]\n"); if (jflag) printf ("}\n"); return EXIT_SUCCESS; } #if HAVE_AUDIT static void log_audit (int type) { int audit_fd = audit_open(); if (audit_fd < 0) { fprintf (stderr, "Failed to connect to audit daemon: %s\n", strerror (errno)); return; } if (audit_log_user_comm_message(audit_fd, type, "", "wtmpdb", NULL, NULL, NULL, 1) < 0) fprintf (stderr, "Failed to send audit message: %s", strerror (errno)); audit_close (audit_fd); } #endif static struct timespec diff_timespec(const struct timespec *time1, const struct timespec *time0) { struct timespec diff = {.tv_sec = time1->tv_sec - time0->tv_sec, .tv_nsec = time1->tv_nsec - time0->tv_nsec}; if (diff.tv_nsec < 0) { diff.tv_nsec += 1000000000; // nsec/sec diff.tv_sec--; } return diff; } #if HAVE_SYSTEMD /* Find out if it was a soft-reboot. With systemd v256 we can query systemd for this. Return values: -1: no systemd support 0: no soft-reboot >0: number of soft-reboots */ static int soft_reboots_count (void) { unsigned soft_reboots_count = -1; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; sd_bus_error error = SD_BUS_ERROR_NULL; int r; if (sd_bus_open_system (&bus) < 0) { fprintf (stderr, "Error: cannot open dbus"); return -1; } r = sd_bus_get_property_trivial (bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "SoftRebootsCount", &error, 'u', &soft_reboots_count); if (r < 0) { /* systemd is too old, don't print error */ if (!sd_bus_error_has_name (&error, SD_BUS_ERROR_UNKNOWN_PROPERTY)) { /* error occured, log it and return to fallback code */ if (error.message) fprintf (stderr, "Failed to get SoftRebootsCount property: %s\n", error.message); } sd_bus_error_free (&error); return -1; } return soft_reboots_count; } #endif static int main_boot (int argc, char **argv) { struct option const longopts[] = { {"file", required_argument, NULL, 'f'}, {"quiet", no_argument, NULL, 'q'}, {NULL, 0, NULL, '\0'} }; char *error = NULL; int c; int soft_reboot = 0; #if HAVE_SYSTEMD int quiet = 0; #endif while ((c = getopt_long (argc, argv, "f:q", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; case 'q': #if HAVE_SYSTEMD quiet = 1; #endif break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); usage (EXIT_FAILURE); } struct utsname uts; uname(&uts); struct timespec ts_now; struct timespec ts_boot; clock_gettime (CLOCK_REALTIME, &ts_now); #ifdef CLOCK_BOOTTIME clock_gettime (CLOCK_BOOTTIME, &ts_boot); #else ts_boot = ts_now; #endif uint64_t time = wtmpdb_timespec2usec (diff_timespec(&ts_now, &ts_boot)); #if HAVE_SYSTEMD struct timespec ts_empty = { .tv_sec = 0, .tv_nsec = 0 }; uint64_t now = wtmpdb_timespec2usec (diff_timespec(&ts_now, &ts_empty)); int count = soft_reboots_count (); if (count > 0) { time = now; soft_reboot = 1; } else if ((count < 0) && ((now - time) > 300 * USEC_PER_SEC) /* 5 minutes */) { if (!quiet) { char timebuf[32]; printf ("Boot time too far in the past, using current time:\n"); format_time (TIMEFMT_CTIME, timebuf, sizeof (timebuf), time/USEC_PER_SEC); printf ("Boot time: %s\n", timebuf); format_time (TIMEFMT_CTIME, timebuf, sizeof (timebuf), now/USEC_PER_SEC); printf ("Current time: %s\n", timebuf); } time = now; soft_reboot = 1; } #endif #if HAVE_AUDIT log_audit (AUDIT_SYSTEM_BOOT); #endif if (wtmpdb_login (wtmpdb_path, BOOT_TIME, soft_reboot ? "soft-reboot" : "reboot", time, "~", uts.release, NULL, &error) < 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "Couldn't write boot entry\n"); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } static int main_boottime (int argc, char **argv) { struct option const longopts[] = { {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; char *error = NULL; int c; uint64_t boottime; while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); usage (EXIT_FAILURE); } boottime = wtmpdb_get_boottime (wtmpdb_path, &error); if (error) { fprintf (stderr, "Couldn't read boot entry: %s\n", error); free (error); exit (EXIT_FAILURE); } char timebuf[32]; format_time (TIMEFMT_CTIME, timebuf, sizeof (timebuf), boottime/USEC_PER_SEC); printf ("system boot %s\n", timebuf); return EXIT_SUCCESS; } static int main_shutdown (int argc, char **argv) { struct option const longopts[] = { {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; char *error = NULL; int c; while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); usage (EXIT_FAILURE); } #if HAVE_AUDIT log_audit (AUDIT_SYSTEM_SHUTDOWN); #endif int64_t id = wtmpdb_get_id (wtmpdb_path, "~", &error); if (id < 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "Couldn't get ID for reboot entry\n"); exit (EXIT_FAILURE); } struct timespec ts; clock_gettime (CLOCK_REALTIME, &ts); uint64_t time = wtmpdb_timespec2usec (ts); if (wtmpdb_logout (wtmpdb_path, id, time, &error) < 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "Couldn't write shutdown entry\n"); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } static int main_import (int argc, char **argv) { struct option const longopts[] = { {"file", required_argument, NULL, 'f'}, {NULL, 0, NULL, '\0'} }; int c; while ((c = getopt_long (argc, argv, "f:", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; default: usage (EXIT_FAILURE); break; } } if (argc == optind) { fprintf (stderr, "No files specified to import.\n"); usage (EXIT_FAILURE); } for (; optind < argc; optind++) if (import_wtmp_file (wtmpdb_path, argv[optind]) == -1) return EXIT_FAILURE; return EXIT_SUCCESS; } int main (int argc, char **argv) { struct option const longopts[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, '\0'} }; int c; if (strcmp (basename(argv[0]), "last") == 0) return main_last (argc, argv); else if (argc == 1) usage (EXIT_SUCCESS); else if (strcmp (argv[1], "last") == 0) return main_last (--argc, ++argv); else if (strcmp (argv[1], "boot") == 0) return main_boot (--argc, ++argv); else if (strcmp (argv[1], "shutdown") == 0) return main_shutdown (--argc, ++argv); else if (strcmp (argv[1], "boottime") == 0) return main_boottime (--argc, ++argv); else if (strcmp (argv[1], "rotate") == 0) return main_rotate (--argc, ++argv); else if (strcmp (argv[1], "import") == 0) return main_import (--argc, ++argv); while ((c = getopt_long (argc, argv, "hv", longopts, NULL)) != -1) { switch (c) { case 'h': usage (EXIT_SUCCESS); break; case 'v': printf ("wtmpdb %s\n", VERSION); break; default: usage (EXIT_FAILURE); break; } } if (argc > optind) { fprintf (stderr, "Unexpected argument: %s\n", argv[optind]); usage (EXIT_FAILURE); } exit (EXIT_SUCCESS); } thkukuk-wtmpdb-edb8638/src/wtmpdbd.c000066400000000000000000000566261477522103000175340ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later /* Copyright (c) 2024, 2025 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 log_level = LOG_WARNING; static int socket_activation = false; static void set_max_log_level (int level) { log_level = level; } static void log_msg (int priority, const char *fmt, ...) { static int is_tty = -1; if (priority > log_level) return; if (is_tty == -1) is_tty = isatty (STDOUT_FILENO); va_list ap; va_start (ap, fmt); if (is_tty) { 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); } static int vl_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { int r; log_msg (LOG_INFO, "Varlink method \"Ping\" called..."); r = sd_varlink_dispatch(link, parameters, NULL, NULL); if (r != 0) return r; return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Alive", true)); } static int vl_method_set_log_level(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[] = { { "Level", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, 0, SD_JSON_MANDATORY }, {} }; int r, level; log_msg(LOG_INFO, "Varlink method \"SetLogLevel\" called..."); r = sd_varlink_dispatch(link, parameters, dispatch_table, &level); if (r != 0) return r; log_msg(LOG_DEBUG, "Log level %i requested", level); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "SetLogLevel: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } set_max_log_level(level); log_msg (LOG_INFO, "New log setting: level=%i", level); return sd_varlink_reply(link, NULL); } static int vl_method_get_environment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { int r; log_msg (LOG_INFO, "Varlink method \"GetEnvironment\" called..."); r = sd_varlink_dispatch(link, parameters, NULL, NULL); if (r != 0) return r; uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "GetEnvironment: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } #if 0 /* XXX */ for (char **e = environ; *e != 0; e++) { if (!env_assignment_is_valid(*e)) goto invalid; if (!utf8_is_valid(*e)) goto invalid; } #endif return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("Environment", environ)); #if 0 invalid: return sd_varlink_error(link, "io.systemd.service.InconsistentEnvironment", parameters); #endif } 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_(freep) char *error = NULL; _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; log_msg (LOG_INFO, "Varlink method \"Login\" called..."); r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r != 0) { log_msg(LOG_ERR, "Login method: varlink dispatch failed: %s", strerror (-r)); return r; } log_msg(LOG_DEBUG, "Requested login record: %i, %s, %li, %s, %s, %s", p.type, p.user, p.usec_login, p.tty, p.rhost, p.service); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "Login: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } 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; 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: varlink dispatch failed: %s", strerror (-r)); return r; } log_msg(LOG_DEBUG, "Logout for entry '%li' at time '%lu' requested", p.id, p.usec_logout); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "Logout: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } 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; 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: varlink dispatch failed: %s", strerror (-r)); return r; } 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; 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: varlink 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 _unused_(**azColName)) { sd_json_variant **array = u; char *endptr; uint64_t logout_t = 0; int r; log_msg(LOG_DEBUG, "wtmpdb_cb_func called for ID %s", argv[0]); if (argc != 8) { log_msg(LOG_ERR, "Invalid number of arguments: got %i, expected 8", argc); incomplete = 1; return 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; } } 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; 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: varlink 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, "Didn't got all entries from db: %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; 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: varlink dispatch failed: %s", strerror (-r)); return r; } log_msg(LOG_DEBUG, "Rotate of database for entries older than '%i' days requested", p.days); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "Rotate: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } _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; 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: varlink dispatch failed: %s", strerror (-r)); return r; } uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "Quit: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } 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(READY) failed: %s", strerror(-r)); } static void announce_stopping (void) { int r = sd_notify (0, "STOPPING=1\n" "STATUS=Shutting down..."); if (r < 0) log_msg (LOG_ERR, "sd_notify(STOPPING) failed: %s", strerror(-r)); } /* event loop which quits after 30 seconds idle time */ #define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) static int varlink_event_loop_with_idle(sd_event *e, sd_varlink_server *s) { int r, code; for (;;) { r = sd_event_get_state(e); if (r < 0) return r; if (r == SD_EVENT_FINISHED) break; r = sd_event_run(e, DEFAULT_EXIT_USEC); if (r < 0) return r; if (r == 0 && (sd_varlink_server_current_connections(s) == 0)) sd_event_exit(e, 0); } r = sd_event_get_exit_code(e, &code); if (r < 0) return r; return code; } static int run_varlink (void) { int r; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = 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 = sd_event_new (&event); if (r < 0) { log_msg (LOG_ERR, "Failed to create new event: %s", strerror (-r)); return r; } r = sd_varlink_server_new (&varlink_server, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA); 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_set_info (varlink_server, NULL, PACKAGE" (wtmpdbd)", VERSION, "https://github.com/thkukuk/wtmpdb"); if (r < 0) 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 interface: %s", strerror(-r)); return r; } r = sd_varlink_server_bind_method_many (varlink_server, "org.openSUSE.wtmpdb.GetBootTime", vl_method_get_boottime, "org.openSUSE.wtmpdb.GetEnvironment", vl_method_get_environment, "org.openSUSE.wtmpdb.GetID", vl_method_get_id, "org.openSUSE.wtmpdb.Login", vl_method_login, "org.openSUSE.wtmpdb.Logout", vl_method_logout, "org.openSUSE.wtmpdb.Ping", vl_method_ping, "org.openSUSE.wtmpdb.Quit", vl_method_quit, "org.openSUSE.wtmpdb.ReadAll", vl_method_read_all, "org.openSUSE.wtmpdb.Rotate", vl_method_rotate, "org.openSUSE.wtmpdb.SetLogLevel", vl_method_set_log_level); if (r < 0) { log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", strerror(-r)); return r; } sd_varlink_server_set_userdata (varlink_server, event); r = sd_varlink_server_attach_event (varlink_server, event, SD_EVENT_PRIORITY_NORMAL); if (r < 0) { log_msg (LOG_ERR, "Failed to attach to event: %s", strerror (-r)); return r; } r = sd_varlink_server_listen_auto (varlink_server); if (r < 0) { log_msg (LOG_ERR, "Failed to listens: %s", strerror (-r)); return r; } if (!socket_activation) { r = sd_varlink_server_listen_address(varlink_server, _VARLINK_WTMPDB_SOCKET, 0666); if (r < 0) { log_msg (LOG_ERR, "Failed to bind to Varlink socket: %s", strerror (-r)); return r; } } announce_ready(); if (socket_activation) r = varlink_event_loop_with_idle(event, varlink_server); else r = sd_event_loop(event); announce_stopping(); return r; } static void print_help (void) { printf("wtmpdbd - manage wtmpdb\n"); printf(" -s, --socket Activation through socket\n"); printf(" -d, --debug Debug mode\n"); printf(" -v, --verbose Verbose logging\n"); printf(" -?, --help Give this help list\n"); printf(" --version Print program version\n"); } int main (int argc, char **argv) { while (1) { int c; int option_index = 0; static struct option long_options[] = { {"socket", no_argument, NULL, 's'}, {"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, "sdvh?", long_options, &option_index); if (c == (-1)) break; switch (c) { case 's': socket_activation = true; break; case 'd': set_max_log_level(LOG_DEBUG); break; case '?': case 'h': print_help (); return 0; case 'v': set_max_log_level(LOG_INFO); 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; } log_msg (LOG_INFO, "Starting wtmpdbd (%s) %s...", PACKAGE, VERSION); int r = run_varlink (); if (r < 0) { log_msg (LOG_ERR, "ERROR: varlink loop failed: %s", strerror (-r)); return -r; } log_msg (LOG_INFO, "wtmpdbd stopped."); return 0; } thkukuk-wtmpdb-edb8638/tests/000077500000000000000000000000001477522103000162635ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/tests/meson.build000066400000000000000000000022661477522103000204330ustar00rootroot00000000000000# This file builds and runs the unit tests libdl = cc.find_library('dl') tst_dlopen_exe = executable('tst-dlopen', 'tst-dlopen.c', dependencies : libdl, include_directories : inc) test('tst-dlopen', tst_dlopen_exe, args : ['pam_wtmpdb.so']) tst_y2038_64bit_time_t = executable('tst-y2038-64bit-time_t', 'tst-y2038-64bit-time_t.c') test('tst-y2038-64bit-time_t', tst_y2038_64bit_time_t) tst_logwtmpdb = executable ('tst-logwtmpdb', 'tst-logwtmpdb.c', include_directories : inc, link_with : libwtmpdb) test('tst-logwtmpdb', tst_logwtmpdb) 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) thkukuk-wtmpdb-edb8638/tests/tst-dlopen.c000066400000000000000000000022141477522103000205170ustar00rootroot00000000000000/* Copyright (C) Nalin Dahyabhai 2003 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. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include "basics.h" /* Simple program to see if dlopen() would succeed. */ int main(int argc, char **argv) { int i; int rc; struct stat st; _cleanup_(freep) char *buf = NULL; for (i = 1; i < argc; i++) { if (dlopen(argv[i], RTLD_NOW)) { fprintf(stdout, "dlopen() of \"%s\" succeeded.\n", argv[i]); } else { rc = asprintf(&buf, "./%s", argv[i]); if (rc >= 0 && (stat(buf, &st) == 0) && dlopen(buf, RTLD_NOW)) { fprintf(stdout, "dlopen() of \"./%s\" " "succeeded.\n", argv[i]); } else { fprintf(stdout, "dlopen() of \"%s\" failed: " "%s\n", argv[i], dlerror()); return 1; } } } return 0; } thkukuk-wtmpdb-edb8638/tests/tst-get_id.c000066400000000000000000000037771477522103000205100ustar00rootroot00000000000000/* 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 #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 == -14) /* SQLITE_CANTOPEN */ return 77; if (id == -2) { printf ("wtmpdb_get_id returned expected: %" PRId64 ", '%s'\n", id, error); return 0; } fprintf (stderr, "wtmpdb_get_id returns '%" PRId64 "' with error message: %s\n", id, error); return 1; } thkukuk-wtmpdb-edb8638/tests/tst-login-logout.c000066400000000000000000000130501477522103000216550ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2023, 2025 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, add logout time. */ #include #include #include #include #include #include "basics.h" #include "wtmpdb.h" static int test_args (const char *db_path, const char *user, const char *tty, const char *rhost, const char *service, const int days) { char *error = NULL; int64_t id; struct timespec ts; uint64_t login_t; uint64_t logout_t; clock_gettime (CLOCK_REALTIME, &ts); ts.tv_sec -= 86400 * days; login_t = wtmpdb_timespec2usec (ts); if ((id = wtmpdb_login (db_path, USER_PROCESS, user, login_t, tty, rhost, service, &error)) < 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "wtmpdb_login failed\n"); return 1; } clock_gettime (CLOCK_REALTIME, &ts); logout_t = wtmpdb_timespec2usec (ts); if (wtmpdb_logout (db_path, id, logout_t, &error) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "wtmpdb_logout failed\n"); return 1; } return 0; } static int counter = 0; static int count_entry (void *unused __attribute__((__unused__)), int argc, char **argv, char **azColName) { (void)argc; (void)argv; (void)azColName; counter++; return 0; } static int test_rotate (const char *db_path, const int days) { int expected; char *error = NULL; counter = 0; if (wtmpdb_read_all (db_path, count_entry, &error) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "wtmpdb_read_all failed\n"); return 1; } expected = (days-1) * 5; if (counter != expected) { fprintf (stderr, "wtmpdb_read_all returned %d expected %d\n", counter, expected); return 1; } if (wtmpdb_rotate (db_path, days, &error, NULL, NULL) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "wtmpdb_rotate failed\n"); return 1; } counter = 0; if (wtmpdb_read_all (db_path, count_entry, &error) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "wtmpdb_read_all failed\n"); return 1; } expected = (days-2) * 5; if (counter != expected) { fprintf (stderr, "wtmpdb_read_all returned %d expected %d\n", counter, expected); return 1; } return 0; } static void remove_backup_db(int days) { _cleanup_(freep) char *backup_path = NULL; struct timespec ts_now; clock_gettime (CLOCK_REALTIME, &ts_now); time_t offset = ts_now.tv_sec - days * 86400; struct tm *tm = localtime (&offset); char date[10]; strftime (date, 10, "%Y%m%d", tm); if (asprintf (&backup_path, "tst-login-logout_%s.db", date) < 0) { fprintf (stderr, "Out of memory"); return; } remove (backup_path); } int main(void) { const char *db_path = "tst-login-logout.db"; /* make sure there is no old stuff flying around. The backup file is not so important. */ remove (db_path); if (test_args (db_path, "user1", "test-tty", "localhost", NULL, 3) != 0) return 1; if (test_args (db_path, "user2", NULL, NULL, NULL, 3) != 0) return 1; if (test_args (db_path, "user3", NULL, NULL, NULL, 3) != 0) return 1; if (test_args (db_path, "user4", "test-tty", NULL, NULL, 3) != 0) return 1; if (test_args (db_path, "user5", NULL, "localhost", NULL, 3) != 0) return 1; if (test_args (db_path, "user1", "test-tty", "localhost", NULL, 2) != 0) return 1; if (test_args (db_path, "user2", NULL, NULL, NULL, 2) != 0) return 1; if (test_args (db_path, "user3", NULL, NULL, NULL, 2) != 0) return 1; if (test_args (db_path, "user4", "test-tty", NULL, NULL, 2) != 0) return 1; if (test_args (db_path, "user5", NULL, "localhost", NULL, 2) != 0) return 1; if (test_rotate (db_path, 3) != 0) return 1; if (test_rotate (db_path, 2) != 0) return 1; /* cleanup */ remove_backup_db(2); remove_backup_db(3); remove (db_path); return 0; } thkukuk-wtmpdb-edb8638/tests/tst-logwtmpdb.c000066400000000000000000000043331477522103000212410ustar00rootroot00000000000000/* 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, add logout time via logwtmpdb() */ #include #include #include #include #include "wtmpdb.h" int main(void) { const char *db_path = "tst-logwtmpdb.db"; char *error = NULL; /* make sure there is no old stuff flying around */ remove (db_path); if (logwtmpdb (db_path, "pts/99", "user", NULL, "test", &error) < 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "logwtmpdb (login) failed\n"); return 1; } if (logwtmpdb (db_path, "pts/99", NULL, NULL, NULL, &error) != 0) { if (error) { fprintf (stderr, "%s\n", error); free (error); } else fprintf (stderr, "logwtmpdb (logout) failed\n"); return 1; } return 0; } thkukuk-wtmpdb-edb8638/tests/tst-varlink.c000066400000000000000000000107361477522103000207140ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2024, 2025 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 #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; if (getuid() != 0) return 77; clock_gettime (CLOCK_REALTIME, &ts); login_t = wtmpdb_timespec2usec (ts); if ((id = wtmpdb_login ("varlink", 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 (%" PRId64 ")\n", id); if (id == -ECONNREFUSED || id == -ENOENT || id == -EACCES || id == -EPROTONOSUPPORT) return 77; return 1; } printf ("wtmpdb_login id: %" PRId64 "\n", id); /* wtmpdb_get_id should return the same ID as wtmpdb_login */ int64_t newid; if ((newid = wtmpdb_get_id ("varlink", 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: %" PRId64 "\n", newid); if (newid != id) { fprintf (stderr, "IDs don't match: %" PRId64 " != %" PRId64 "\n", id, newid); return 1; } clock_gettime (CLOCK_REALTIME, &ts); logout_t = wtmpdb_timespec2usec (ts); if (wtmpdb_logout ("varlink", 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 ("varlink", &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 ("varlink", 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 if (entries == 0) printf ("Nothing to move for wtmpdb_rotate\n"); else printf ("wtmpdb_rotate moved %" PRIu64 " entries into %s\n", entries, backup); return 0; } thkukuk-wtmpdb-edb8638/tests/tst-y2038-64bit-time_t.c000066400000000000000000000031441477522103000222330ustar00rootroot00000000000000/* 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: Verify that time_t is at least 64bit */ #include #include int main(void) { if (sizeof (time_t) < 8) { fprintf (stderr, "ERROR: time_t does not have at least 64bit\n"); return 1; } return 0; } thkukuk-wtmpdb-edb8638/tmpfiles.d/000077500000000000000000000000001477522103000171665ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/tmpfiles.d/meson.build000066400000000000000000000000671477522103000213330ustar00rootroot00000000000000install_data('wtmpdb.conf', install_dir : tmpfilesdir) thkukuk-wtmpdb-edb8638/tmpfiles.d/wtmpdb.conf000066400000000000000000000001401477522103000213250ustar00rootroot00000000000000# This file is part of wtmpdb. # # See tmpfiles.d(5) for details # d /var/lib/wtmpdb 0755 - - - thkukuk-wtmpdb-edb8638/units/000077500000000000000000000000001477522103000162635ustar00rootroot00000000000000thkukuk-wtmpdb-edb8638/units/meson.build000066400000000000000000000005371477522103000204320ustar00rootroot00000000000000install_data('wtmpdb-update-boot.service', install_dir : systemunitdir) install_data('wtmpdb-rotate.service', install_dir : systemunitdir) install_data('wtmpdb-rotate.timer', install_dir : systemunitdir) if have_systemd257 install_data('wtmpdbd.service', install_dir : systemunitdir) install_data('wtmpdbd.socket', install_dir : systemunitdir) endif thkukuk-wtmpdb-edb8638/units/wtmpdb-rotate.service000066400000000000000000000003251477522103000224360ustar00rootroot00000000000000[Unit] Description=Rotate wtmpdb Documentation=man:wtmpdb(8) RequiresMountsFor=/var/lib/wtmpdb [Service] Type=oneshot ExecStart=/usr/bin/wtmpdb rotate Nice=19 IOSchedulingClass=best-effort IOSchedulingPriority=7 thkukuk-wtmpdb-edb8638/units/wtmpdb-rotate.timer000066400000000000000000000002571477522103000221220ustar00rootroot00000000000000[Unit] Description=Monthly rotation of wtmpdb Documentation=man:wtmpdb(8) [Timer] OnCalendar=monthly RandomizedDelaySec=900 Persistent=true [Install] WantedBy=timers.target thkukuk-wtmpdb-edb8638/units/wtmpdb-update-boot.service000066400000000000000000000007231477522103000233650ustar00rootroot00000000000000[Unit] Description=Write boot and shutdown times into wtmpdb Documentation=man:wtmpdb(8) DefaultDependencies=no RequiresMountsFor=/var/lib/wtmpdb Conflicts=shutdown.target After=systemd-remount-fs.service systemd-tmpfiles-setup.service Before=shutdown.target # Ask for the dbus socket. Wants=dbus.socket After=dbus.socket [Service] Type=oneshot ExecStart=/usr/bin/wtmpdb boot ExecStop=/usr/bin/wtmpdb shutdown RemainAfterExit=true [Install] WantedBy=default.target thkukuk-wtmpdb-edb8638/units/wtmpdbd.service000066400000000000000000000012411477522103000213040ustar00rootroot00000000000000[Unit] Description=wtmpdb daemon Documentation=man:wtmpdbd(8) [Service] Type=notify Environment="WTMPDBD_OPTS=" EnvironmentFile=-/etc/default/wtmpdbd ExecStart=/usr/libexec/wtmpdbd -s $WTMPDBD_OPTS IPAddressDeny=any LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes PrivateDevices=yes PrivateNetwork=yes PrivateTmp=yes ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectProc=invisible ProtectSystem=strict RestrictRealtime=true ReadWritePaths=/run/wtmpdb /var/lib/wtmpdb RestrictAddressFamilies=AF_UNIX RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes thkukuk-wtmpdb-edb8638/units/wtmpdbd.socket000066400000000000000000000003211477522103000211320ustar00rootroot00000000000000[Unit] Description=wtmpdb daemon socket Documentation=man:wtmpdbd(8) [Socket] ListenStream=/run/wtmpdb/socket FileDescriptorName=varlink SocketMode=0666 DirectoryMode=0755 [Install] WantedBy=sockets.target