pax_global_header00006660000000000000000000000064146507233520014521gustar00rootroot0000000000000052 comment=b2a2b7649f1c78a4afd5eb0268434491d2099da8 thkukuk-wtmpdb-b2a2b76/000077500000000000000000000000001465072335200151115ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/.github/000077500000000000000000000000001465072335200164515ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/.github/workflows/000077500000000000000000000000001465072335200205065ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/.github/workflows/meson-sanitizer.yml000066400000000000000000000011631465072335200243610ustar00rootroot00000000000000name: meson sanitizer on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - name: Install pam-devel run: sudo apt-get install libpam0g-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-b2a2b76/.github/workflows/meson.yml000066400000000000000000000007301465072335200223520ustar00rootroot00000000000000name: 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 - 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-b2a2b76/.gitignore000066400000000000000000000001641465072335200171020ustar00rootroot00000000000000# Object files *.o *.ko *.obj *.elf # Libraries *.lib *.a *.la *.lo # Shared objects *.so *.so.* # Misc build *~ thkukuk-wtmpdb-b2a2b76/INSTALL.md000066400000000000000000000005731465072335200165460ustar00rootroot00000000000000# 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-b2a2b76/LICENSE000066400000000000000000000024271465072335200161230ustar00rootroot00000000000000BSD 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-b2a2b76/NEWS000066400000000000000000000033671465072335200156210ustar00rootroot00000000000000Version 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-b2a2b76/README.md000066400000000000000000000101661465072335200163740ustar00rootroot00000000000000# 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. **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 and an application: * `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`). By default the database will be written as `/var/lib/wtmpdb/wtmpdb.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. ## 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. ### systemd service The `wtmpdb-update-boot.service` will record the boot and shutdown times of a service. thkukuk-wtmpdb-b2a2b76/include/000077500000000000000000000000001465072335200165345ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/include/wtmpdb.h000066400000000000000000000055621465072335200202120ustar00rootroot00000000000000/* 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 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) 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_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); thkukuk-wtmpdb-b2a2b76/lib/000077500000000000000000000000001465072335200156575ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/lib/libwtmpdb.map000066400000000000000000000004301465072335200203370ustar00rootroot00000000000000LIBWTMPDB_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; thkukuk-wtmpdb-b2a2b76/lib/logwtmpdb.c000066400000000000000000000047571465072335200200370ustar00rootroot00000000000000/* 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 ? db_path : _PATH_WTMPDB, USER_PROCESS, name, time, tty, host, service, error); } else { /* logout */ int64_t id = wtmpdb_get_id (db_path ? db_path : _PATH_WTMPDB, tty, error); retval = wtmpdb_logout (db_path ? db_path : _PATH_WTMPDB, id, time, error); } return retval; } thkukuk-wtmpdb-b2a2b76/lib/sqlite.c000066400000000000000000000455251465072335200173370ustar00rootroot00000000000000/* 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 #include #include "wtmpdb.h" #define TIMEOUT 5000 /* 5 sec */ /* Begin - local helper functions */ static void mkdir_p(const char *pathname, mode_t mode) { if (mkdir(pathname, mode) == 0 || errno == EEXIST || errno != ENOENT) return; char *buf = strdup(pathname); mkdir_p(dirname(buf), mode); free(buf); mkdir(pathname, mode); } 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; } } } /* End - local helper functions */ static sqlite3 * open_database_ro (const char *path, char **error) { sqlite3 *db; if (sqlite3_open_v2 (path, &db, SQLITE_OPEN_READONLY, NULL) != 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); return NULL; } sqlite3_busy_timeout(db, TIMEOUT); return db; } static sqlite3 * open_database_rw (const char *path, char **error) { sqlite3 *db; char *buf = strdup(path); mkdir_p(dirname(buf), 0644); free(buf); if (sqlite3_open (path, &db) != 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); return NULL; } sqlite3_busy_timeout(db, TIMEOUT); return db; } /* 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 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) { sqlite3 *db; int64_t retval; if ((db = open_database_rw (db_path?db_path:_PATH_WTMPDB, error)) == NULL) return -1; retval = add_entry (db, type, user, usec_login, tty, rhost, service, error); sqlite3_close (db); return retval; } /* Updates logout field. logout timestamp is in usec. Returns 0 on success, -1 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, -1 on failure. */ int wtmpdb_logout (const char *db_path, int64_t id, uint64_t usec_logout, char **error) { sqlite3 *db; int retval; if ((db = open_database_rw (db_path?db_path:_PATH_WTMPDB, error)) == NULL) return -1; retval = update_logout (db, id, usec_logout, error); sqlite3_close (db); return retval; } 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) { if (error) if (asprintf (error, "wtmpdb_logout: Failed to execute statement: %s", sqlite3_errmsg (db)) < 0) *error = strdup ("wtmpdb_logout: Out of memory"); return -1; } if (sqlite3_bind_text (res, 1, tty, -1, SQLITE_STATIC) != SQLITE_OK) { if (error) if (asprintf (error, "wtmpdb_logout: Failed to create search query: %s", sqlite3_errmsg (db)) < 0) *error = strdup("wtmpdb_logout: Out of memory"); sqlite3_finalize(res); return -1; } int step = sqlite3_step (res); if (step == SQLITE_ROW) id = sqlite3_column_int64 (res, 0); else { if (error) if (asprintf (error, "wtmpdb_logout: TTY '%s' without logout time not found (%d)", tty, step) < 0) *error = strdup("wtmpdb_logout: Out of memory"); sqlite3_finalize (res); return -1; } sqlite3_finalize (res); return id; } int64_t wtmpdb_get_id (const char *db_path, const char *tty, char **error) { sqlite3 *db; int64_t retval; if ((db = open_database_ro (db_path?db_path:_PATH_WTMPDB, error)) == NULL) return -1; 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 wtmpdb_read_all (const char *db_path, int (*cb_func)(void *unused, int argc, char **argv, char **azColName), char **error) { sqlite3 *db; char *err_msg = 0; if ((db = open_database_ro (db_path?db_path:_PATH_WTMPDB, error)) == NULL) return -1; char *sql = "SELECT * FROM wtmp ORDER BY Login DESC, Logout ASC"; if (sqlite3_exec (db, sql, cb_func, NULL, &err_msg) != SQLITE_OK) { if (error) if (asprintf (error, "wtmpdb_read_all: SQL error: %s", err_msg) < 0) *error = strdup ("wtmpdb_read_all: Out of memory"); sqlite3_free (err_msg); sqlite3_close (db); return -1; } sqlite3_close (db); 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, -1 on failure. */ int wtmpdb_rotate (const char *db_path, const int days, char **error, char **wtmpdb_name, uint64_t *entries) { sqlite3 *db_src; sqlite3 *db_dest; uint64_t counter = 0; struct timespec ts_now; clock_gettime (CLOCK_REALTIME, &ts_now); time_t offset = ts_now.tv_sec - days * 86400; struct tm *tm = localtime (&offset); uint64_t login_t = offset * USEC_PER_SEC; char date[10]; strftime (date, 10, "%Y%m%d", tm); char *dest_path = NULL; char *dest_file = strdup(db_path); strip_extension(dest_file); if (asprintf (&dest_path, "%s/%s_%s.db", dirname(dest_file), basename(dest_file), date) < 0) { *error = strdup ("wtmpdb_rotate: Out of memory"); return -1; } if ((db_dest = open_database_rw (dest_path, error)) == NULL) { free(dest_path); free(dest_file); return -1; } if ((db_src = open_database_rw (db_path?db_path:_PATH_WTMPDB, error)) == NULL) { free(dest_path); free(dest_file); sqlite3_close (db_dest); return -1; } 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, "wtmpdb_rotate: Failed to execute statement %s: %s", sql_select, sqlite3_errmsg (db_src)) < 0) *error = strdup ("wtmpdb_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, "wtmpdb_rotate: Failed to create replace statement for login time: %s", sqlite3_errmsg (db_src)) < 0) *error = strdup("wtmpdb_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, "wtmpdb_rotate: SQL error: %s", sqlite3_errmsg(db_src)) < 0) *error = strdup ("wtmpdb_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, "wtmpdb_rotate: Failed to execute statement %s: %s", sql_delete, sqlite3_errmsg (db_src)) < 0) *error = strdup ("wtmpdb_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, "wtmpdb_rotate: Failed to create replace statement for login time: %s", sqlite3_errmsg (db_src)) < 0) *error = strdup("wtmpdb_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, "wtmpdb_rotate: Adding an entry did not return SQLITE_DONE: %d", step) < 0) *error = strdup("wtmpdb_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; } uint64_t wtmpdb_get_boottime (const char *db_path, char **error) { sqlite3 *db; uint64_t retval; if ((db = open_database_ro (db_path?db_path:_PATH_WTMPDB, error)) == NULL) return 0; retval = search_boottime (db, error); sqlite3_close (db); return retval; } thkukuk-wtmpdb-b2a2b76/man/000077500000000000000000000000001465072335200156645ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/man/custom-man.xsl000066400000000000000000000007001465072335200204740ustar00rootroot00000000000000 thkukuk-wtmpdb-b2a2b76/man/meson.build000066400000000000000000000020261465072335200200260ustar00rootroot00000000000000want_man = get_option('man') xsltproc_exe = find_program('xsltproc', required : want_man == 'true') want_man = want_man != 'false' 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) endif thkukuk-wtmpdb-b2a2b76/man/pam_wtmpdb.8.xml000066400000000000000000000117021465072335200207070ustar00rootroot00000000000000 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/wtmpdb.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/wtmpdb.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/wtmpdb.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-b2a2b76/man/wtmpdb.8.xml000066400000000000000000000227301465072335200200550ustar00rootroot00000000000000 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 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). 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. global options global options Display help message and exit. Print version number and exit. FILES /var/lib/wtmpdb/wtmpdb.db Wtmpdb logging database file SEE ALSO pam_wtmpdb8 , AUTHOR wtmpdb was written by Thorsten Kukuk <kukuk@suse.com>. thkukuk-wtmpdb-b2a2b76/meson.build000066400000000000000000000117031465072335200172550ustar00rootroot00000000000000project( '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=3',], license : ['BSD-2-Clause',], version : '0.13.0', ) conf = configuration_data() conf.set_quoted('PROJECT_VERSION', meson.project_version()) cc = meson.get_compiler('c') pkg = import('pkgconfig') inc = include_directories('include') 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 systemunitdir = prefixdir / 'lib/systemd/system' tmpfilesdir = prefixdir / 'lib/tmpfiles.d' libpam = cc.find_library('pam') libsqlite3 = cc.find_library('sqlite3') want_audit = get_option('audit') if want_audit != 'false' libaudit = dependency('audit', required : want_audit == 'true') have_audit = libaudit.found() else have_audit = false libaudit = [] endif conf.set10('HAVE_AUDIT', have_audit) want_systemd = get_option('systemd') if want_systemd != 'false' libsystemd = dependency('libsystemd', required : want_systemd == 'true') have_systemd = libsystemd.found() else have_systemd = false libsystemd = [] endif conf.set10('HAVE_SYSTEMD', have_systemd) libwtmpdb_c = files('lib/logwtmpdb.c', 'lib/sqlite.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], 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'] 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-b2a2b76/meson_options.txt000066400000000000000000000017671465072335200205610ustar00rootroot00000000000000option('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/x86_64-linux-gnu or such''') option('pamlibdir', type : 'string', description : 'directory for PAM modules') option('man', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto', description : 'build and install man pages') option('audit', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto', description : 'libaudit support') option('systemd', type : 'combo', choices : ['auto', 'true', 'false'], value : 'auto', description : 'systemd support to detect soft-reboots') option('compat-symlink', type : 'boolean', value : false, description : 'create last compat symlink') thkukuk-wtmpdb-b2a2b76/src/000077500000000000000000000000001465072335200157005ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/src/pam_wtmpdb.c000066400000000000000000000221561465072335200202040ustar00rootroot00000000000000/* 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-b2a2b76/src/pam_wtmpdb.map000066400000000000000000000002471465072335200205340ustar00rootroot00000000000000{ 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-b2a2b76/src/wtmpdb.c000066400000000000000000000704431465072335200173510ustar00rootroot00000000000000/* 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 "wtmpdb.h" static char *wtmpdb_path = _PATH_WTMPDB; #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 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 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) { struct tm res; if (strcmp (str, "today") == 0) { time_t t = time (NULL); localtime_r (&t, &res); res.tm_isdst = -1; res.tm_sec = res.tm_min = res.tm_hour = 0; } else if (strcmp (str, "yesterday") == 0) { time_t t = time (NULL); localtime_r (&t, &res); res.tm_isdst = -1; res.tm_mday--; res.tm_sec = res.tm_min = res.tm_hour = 0; } else { char *r = strptime (str, "%Y-%m-%d %T", &res); if (r == NULL || *r != '\0') { r = strptime (str, "%Y-%m-%d", &res); if (r == NULL || *r != '\0') return -1; } } *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 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) { 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\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 (" -n, --limit 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 ("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, 'u'}, {"time-format", required_argument, NULL, TIMEFMT_VALUE}, {NULL, 0, NULL, '\0'} }; int time_fmt = TIMEFMT_CTIME; char *error = NULL; int c; while ((c = getopt_long (argc, argv, "adf:Fin:p:RSs:t:wx", longopts, NULL)) != -1) { switch (c) { 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 '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 'u': 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 (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) printf ("%s has no entries\n", wtmpdb_path); else if (time_fmt != TIMEFMT_NOTIME) { char wtmptime[32]; format_time (time_fmt, wtmptime, sizeof (wtmptime), wtmp_start/USEC_PER_SEC); printf ("\n%s begins %s\n", wtmpdb_path, wtmptime); } 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 quiet = 0; int soft_reboot = 0; while ((c = getopt_long (argc, argv, "f:q", longopts, NULL)) != -1) { switch (c) { case 'f': wtmpdb_path = optarg; break; case 'q': quiet = 1; 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); clock_gettime (CLOCK_BOOTTIME, &ts_boot); 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; } 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); while ((c = getopt_long (argc, argv, "hv", longopts, NULL)) != -1) { switch (c) { case 'h': usage (EXIT_SUCCESS); break; case 'v': printf ("wtmpdb %s\n", PROJECT_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-b2a2b76/tests/000077500000000000000000000000001465072335200162535ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/tests/meson.build000066400000000000000000000014131465072335200204140ustar00rootroot00000000000000# This file builds and runs the unit tests libdl = cc.find_library('dl') tst_dlopen_exe = executable('tst-dlopen', 'tst-dlopen.c', dependencies : libdl) 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) thkukuk-wtmpdb-b2a2b76/tests/tst-dlopen.c000066400000000000000000000021331465072335200205070ustar00rootroot00000000000000/* 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 /* Simple program to see if dlopen() would succeed. */ int main(int argc, char **argv) { int i; struct stat st; char buf[PATH_MAX]; for (i = 1; i < argc; i++) { if (dlopen(argv[i], RTLD_NOW)) { fprintf(stdout, "dlopen() of \"%s\" succeeded.\n", argv[i]); } else { snprintf(buf, sizeof(buf), "./%s", argv[i]); if ((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-b2a2b76/tests/tst-login-logout.c000066400000000000000000000125441465072335200216540ustar00rootroot00000000000000/* 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. */ #include #include #include #include #include #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) { 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; } if (counter != ((days-1) * 5)) { fprintf (stderr, "wtmpdb_read_all returned %d expected 5\n", counter); 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; } if (counter != (days-2) * 5) { fprintf (stderr, "wtmpdb_read_all returned %d expected 0\n", counter); return 1; } return 0; } 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, 4) != 0) return 1; if (test_args (db_path, "user2", NULL, NULL, NULL, 4) != 0) return 1; if (test_args (db_path, "user3", NULL, NULL, NULL, 4) != 0) return 1; if (test_args (db_path, "user4", "test-tty", NULL, NULL, 4) != 0) return 1; if (test_args (db_path, "user5", NULL, "localhost", NULL, 4) != 0) return 1; 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_rotate (db_path, 3) != 0) return 1; if (test_rotate (db_path, 2) != 0) return 1; /* cleanup */ struct timespec ts_now; clock_gettime (CLOCK_REALTIME, &ts_now); time_t offset = ts_now.tv_sec - 2 * 86400; struct tm *tm = localtime (&offset); char date[10]; strftime (date, 10, "%Y%m%d", tm); char *backup_path = NULL; if (asprintf (&backup_path, "tst-login-logout_%s.db", date) < 0) { fprintf (stderr, "Out of memory"); return 1; } remove (backup_path); free (backup_path); remove (db_path); return 0; } thkukuk-wtmpdb-b2a2b76/tests/tst-logwtmpdb.c000066400000000000000000000043331465072335200212310ustar00rootroot00000000000000/* 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-b2a2b76/tests/tst-y2038-64bit-time_t.c000066400000000000000000000031441465072335200222230ustar00rootroot00000000000000/* 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-b2a2b76/tmpfiles.d/000077500000000000000000000000001465072335200171565ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/tmpfiles.d/meson.build000066400000000000000000000000671465072335200213230ustar00rootroot00000000000000install_data('wtmpdb.conf', install_dir : tmpfilesdir) thkukuk-wtmpdb-b2a2b76/tmpfiles.d/wtmpdb.conf000066400000000000000000000001401465072335200213150ustar00rootroot00000000000000# This file is part of wtmpdb. # # See tmpfiles.d(5) for details # d /var/lib/wtmpdb 0755 - - - thkukuk-wtmpdb-b2a2b76/units/000077500000000000000000000000001465072335200162535ustar00rootroot00000000000000thkukuk-wtmpdb-b2a2b76/units/meson.build000066400000000000000000000003141465072335200204130ustar00rootroot00000000000000install_data('wtmpdb-update-boot.service', install_dir : systemunitdir) install_data('wtmpdb-rotate.service', install_dir : systemunitdir) install_data('wtmpdb-rotate.timer', install_dir : systemunitdir) thkukuk-wtmpdb-b2a2b76/units/wtmpdb-rotate.service000066400000000000000000000003251465072335200224260ustar00rootroot00000000000000[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-b2a2b76/units/wtmpdb-rotate.timer000066400000000000000000000002571465072335200221120ustar00rootroot00000000000000[Unit] Description=Monthly rotation of wtmpdb Documentation=man:wtmpdb(8) [Timer] OnCalendar=monthly RandomizedDelaySec=900 Persistent=true [Install] WantedBy=timers.target thkukuk-wtmpdb-b2a2b76/units/wtmpdb-update-boot.service000066400000000000000000000006431465072335200233560ustar00rootroot00000000000000[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=sysinit.target shutdown.target [Service] Type=oneshot ExecStart=/usr/bin/wtmpdb boot ExecStop=/usr/bin/wtmpdb shutdown RemainAfterExit=true [Install] WantedBy=default.target