From c2e299d0acb2fa4ad1691452fa0eae76520bbdb0 Mon Sep 17 00:00:00 2001 From: Stefan Schubert Date: Thu, 7 Sep 2023 17:30:23 +0200 Subject: [PATCH] lastlog2 - Y2038 safe version of lastlog pam_lastlog2 - PAM module which logs user login with lastlog2 --- .github/workflows/cibuild-setup-ubuntu.sh | 1 + .gitignore | 2 + .packit.yaml | 2 +- AUTHORS | 1 + Makefile.am | 11 +- bash-completion/Makemodule.am | 3 + bash-completion/lastlog2 | 57 ++ configure.ac | 49 ++ liblastlog2/COPYING | 24 + liblastlog2/Makemodule.am | 10 + liblastlog2/README.md | 40 ++ liblastlog2/lastlog2.pc.in | 11 + liblastlog2/man/Makemodule.am | 20 + liblastlog2/man/lastlog2.3.adoc | 51 ++ liblastlog2/man/ll2_import_lastlog.3.adoc | 65 ++ liblastlog2/man/ll2_new_context.3.adoc | 61 ++ liblastlog2/man/ll2_read_all.3.adoc | 74 +++ liblastlog2/man/ll2_read_entry.3.adoc | 72 +++ liblastlog2/man/ll2_remove_entry.3.adoc | 63 ++ liblastlog2/man/ll2_rename_user.3.adoc | 66 ++ liblastlog2/man/ll2_unref_context.3.adoc | 57 ++ liblastlog2/man/ll2_update_login_time.3.adoc | 67 ++ liblastlog2/man/ll2_write_entry.3.adoc | 70 +++ liblastlog2/meson.build | 63 ++ liblastlog2/src/Makemodule.am | 99 +++ liblastlog2/src/lastlog2.c | 594 ++++++++++++++++++ liblastlog2/src/lastlog2.h | 91 +++ liblastlog2/src/lastlog2P.h | 37 ++ liblastlog2/src/liblastlog2.sym | 13 + liblastlog2/src/tests/tst_dlopen.c | 40 ++ .../src/tests/tst_pam_lastlog2_output.c | 115 ++++ liblastlog2/src/tests/tst_remove_entry.c | 88 +++ liblastlog2/src/tests/tst_rename_user.c | 112 ++++ liblastlog2/src/tests/tst_write_read_user.c | 165 +++++ .../src/tests/tst_y2038_ll2_read_all.c | 156 +++++ .../src/tests/tst_y2038_sqlite3_time.c | 83 +++ meson.build | 30 + meson_options.txt | 8 + misc-utils/.gitignore | 2 + misc-utils/Makemodule.am | 14 + misc-utils/lastlog2-import.service.in | 15 + misc-utils/lastlog2.8.adoc | 96 +++ misc-utils/lastlog2.c | 337 ++++++++++ misc-utils/lastlog2.conf.in | 5 + misc-utils/meson.build | 25 + pam_lastlog2/COPYING | 24 + pam_lastlog2/Makemodule.am | 8 + pam_lastlog2/man/Makemodule.am | 6 + pam_lastlog2/man/pam_lastlog2.8.adoc | 78 +++ pam_lastlog2/meson.build | 39 ++ pam_lastlog2/src/Makemodule.am | 21 + pam_lastlog2/src/pam_lastlog2.c | 340 ++++++++++ pam_lastlog2/src/pam_lastlog2.sym | 11 + tests/commands.sh | 7 + tests/ts/liblastlog2/dlopen | 23 + tests/ts/liblastlog2/pam_lastlog2_output | 17 + tests/ts/liblastlog2/remove_entry | 15 + tests/ts/liblastlog2/rename_user | 15 + tests/ts/liblastlog2/sqlite3_time | 15 + tests/ts/liblastlog2/write_read_user | 15 + tests/ts/liblastlog2/y2038_ll2_read_all | 15 + 61 files changed, 3710 insertions(+), 4 deletions(-) create mode 100644 bash-completion/lastlog2 create mode 100644 liblastlog2/COPYING create mode 100644 liblastlog2/Makemodule.am create mode 100644 liblastlog2/README.md create mode 100644 liblastlog2/lastlog2.pc.in create mode 100644 liblastlog2/man/Makemodule.am create mode 100644 liblastlog2/man/lastlog2.3.adoc create mode 100644 liblastlog2/man/ll2_import_lastlog.3.adoc create mode 100644 liblastlog2/man/ll2_new_context.3.adoc create mode 100644 liblastlog2/man/ll2_read_all.3.adoc create mode 100644 liblastlog2/man/ll2_read_entry.3.adoc create mode 100644 liblastlog2/man/ll2_remove_entry.3.adoc create mode 100644 liblastlog2/man/ll2_rename_user.3.adoc create mode 100644 liblastlog2/man/ll2_unref_context.3.adoc create mode 100644 liblastlog2/man/ll2_update_login_time.3.adoc create mode 100644 liblastlog2/man/ll2_write_entry.3.adoc create mode 100644 liblastlog2/meson.build create mode 100644 liblastlog2/src/Makemodule.am create mode 100644 liblastlog2/src/lastlog2.c create mode 100644 liblastlog2/src/lastlog2.h create mode 100644 liblastlog2/src/lastlog2P.h create mode 100644 liblastlog2/src/liblastlog2.sym create mode 100644 liblastlog2/src/tests/tst_dlopen.c create mode 100644 liblastlog2/src/tests/tst_pam_lastlog2_output.c create mode 100644 liblastlog2/src/tests/tst_remove_entry.c create mode 100644 liblastlog2/src/tests/tst_rename_user.c create mode 100644 liblastlog2/src/tests/tst_write_read_user.c create mode 100644 liblastlog2/src/tests/tst_y2038_ll2_read_all.c create mode 100644 liblastlog2/src/tests/tst_y2038_sqlite3_time.c create mode 100644 misc-utils/lastlog2-import.service.in create mode 100644 misc-utils/lastlog2.8.adoc create mode 100644 misc-utils/lastlog2.c create mode 100644 misc-utils/lastlog2.conf.in create mode 100644 pam_lastlog2/COPYING create mode 100644 pam_lastlog2/Makemodule.am create mode 100644 pam_lastlog2/man/Makemodule.am create mode 100644 pam_lastlog2/man/pam_lastlog2.8.adoc create mode 100644 pam_lastlog2/meson.build create mode 100644 pam_lastlog2/src/Makemodule.am create mode 100644 pam_lastlog2/src/pam_lastlog2.c create mode 100644 pam_lastlog2/src/pam_lastlog2.sym create mode 100755 tests/ts/liblastlog2/dlopen create mode 100755 tests/ts/liblastlog2/pam_lastlog2_output create mode 100755 tests/ts/liblastlog2/remove_entry create mode 100755 tests/ts/liblastlog2/rename_user create mode 100755 tests/ts/liblastlog2/sqlite3_time create mode 100755 tests/ts/liblastlog2/write_read_user create mode 100755 tests/ts/liblastlog2/y2038_ll2_read_all diff --git a/.github/workflows/cibuild-setup-ubuntu.sh b/.github/workflows/cibuild-setup-ubuntu.sh index 34774d3a42..7e75025d10 100755 --- a/.github/workflows/cibuild-setup-ubuntu.sh +++ b/.github/workflows/cibuild-setup-ubuntu.sh @@ -32,6 +32,7 @@ PACKAGES=( gawk bison flex + libsqlite3-dev ) PACKAGES_OPTIONAL=( diff --git a/.gitignore b/.gitignore index aa33f35aaa..3af79d1325 100644 --- a/.gitignore +++ b/.gitignore @@ -203,3 +203,5 @@ ylwrap /wipefs /write /zramctl +/lsclocks +/lastlog2 diff --git a/.packit.yaml b/.packit.yaml index 9409d62090..0229a9cc44 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -24,7 +24,7 @@ actions: # Drop all patches, since they're already included in the tarball - "sed -ri '/^Patch[0-9]+:/d' .packit_rpm/util-linux.spec" # Install additional dependencies we need for the build/tests - - "sed -i '/^### Dependencies/aBuildRequires: autoconf automake bc bison flex iproute libtool procps-ng socat' .packit_rpm/util-linux.spec" + - "sed -i '/^### Dependencies/aBuildRequires: autoconf automake bc bison flex iproute libtool procps-ng socat sqlite-devel' .packit_rpm/util-linux.spec" # We need to call autogen, since we use a custom tarball - "sed -i '/^unset LINGUAS/a./autogen.sh' .packit_rpm/util-linux.spec" # Enable tests after build diff --git a/AUTHORS b/AUTHORS index 64cddb1735..8e3da1f24a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,6 +42,7 @@ AUTHORS (merged projects & commands): Theodore Ts'o libmount: Karel Zak libuuid: Theodore Ts'o + liblastlog2: Thorsten Kukuk lscpu: Cai Qian lsclocks: Thomas Weißschuh lsblk: Milan Broz diff --git a/Makefile.am b/Makefile.am index 2df89ff6df..796e82fabd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,7 +41,7 @@ ul_libblkid_incdir = $(top_builddir)/libblkid/src ul_libmount_incdir = $(top_builddir)/libmount/src ul_libsmartcols_incdir = $(top_builddir)/libsmartcols/src ul_libfdisk_incdir = $(top_builddir)/libfdisk/src - +ul_liblastlog2_incdir = $(top_srcdir)/liblastlog2/src ul_libuuid_incdir = $(top_srcdir)/libuuid/src bashcompletiondir = @bashcompletiondir@ @@ -59,6 +59,7 @@ bin_PROGRAMS = sbin_PROGRAMS = dist_usrbin_exec_SCRIPTS = systemdsystemunit_DATA = +tmpfiles_DATA = dist_bashcompletion_DATA = check_PROGRAMS = dist_check_SCRIPTS = @@ -103,6 +104,7 @@ include tools/Makemodule.am include include/Makemodule.am include lib/Makemodule.am include libuuid/Makemodule.am +include liblastlog2/Makemodule.am include libblkid/Makemodule.am include libmount/Makemodule.am include libsmartcols/Makemodule.am @@ -116,6 +118,7 @@ include sys-utils/Makemodule.am include misc-utils/Makemodule.am include disk-utils/Makemodule.am +include pam_lastlog2/Makemodule.am include bash-completion/Makemodule.am include man-common/Makemodule.am @@ -138,6 +141,7 @@ EXTRA_DIST += \ po/meson.build \ lib/meson.build \ libuuid/meson.build \ + liblastlog2/meson.build \ sys-utils/meson.build \ libfdisk/meson.build \ term-utils/meson.build \ @@ -176,6 +180,7 @@ edit_cmd = sed \ -e 's|@VERSION[@]|$(VERSION)|g' \ -e 's|@ADJTIME_PATH[@]|$(ADJTIME_PATH)|g' \ -e 's|@LIBUUID_VERSION[@]|$(LIBUUID_VERSION)|g' \ + -e 's|@LIBLASTLOG2_VERSION[@]|$(LIBLASTLOG2_VERSION)|g' \ -e 's|@LIBMOUNT_VERSION[@]|$(LIBMOUNT_VERSION)|g' \ -e 's|@LIBMOUNT_MAJOR_VERSION[@]|$(LIBMOUNT_MAJOR_VERSION)|g' \ -e 's|@LIBMOUNT_MINOR_VERSION[@]|$(LIBMOUNT_MINOR_VERSION)|g' \ @@ -346,8 +351,8 @@ DISTCHECK_CONFIGURE_FLAGS = \ --enable-gtk-doc \ --with-python \ --with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir) \ - --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) - + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) \ + --with-tmpfilesdir=$$dc_install_base/$(tmpfilesdir) BUILT_SOURCES += .version .version: diff --git a/bash-completion/Makemodule.am b/bash-completion/Makemodule.am index b4a6db1962..034f9434aa 100644 --- a/bash-completion/Makemodule.am +++ b/bash-completion/Makemodule.am @@ -201,6 +201,9 @@ endif if BUILD_UUIDGEN dist_bashcompletion_DATA += bash-completion/uuidgen endif +if BUILD_LIBLASTLOG2 +dist_bashcompletion_DATA += bash-completion/lastlog2 +endif if BUILD_UUIDPARSE dist_bashcompletion_DATA += bash-completion/uuidparse endif diff --git a/bash-completion/lastlog2 b/bash-completion/lastlog2 new file mode 100644 index 0000000000..09aa4ed11e --- /dev/null +++ b/bash-completion/lastlog2 @@ -0,0 +1,57 @@ +_lastlog2_module() +{ + local cur prev OPTS + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + case $prev in + '-b'|'--before') + COMPREPLY=( $(compgen -W "days" -- $cur) ) + return 0 + ;; + '-t'|'--time') + COMPREPLY=( $(compgen -W "days" -- $cur) ) + return 0 + ;; + '-i'|'--import') + COMPREPLY=( $(compgen -W "file" -- "$cur") ) + return 0 + ;; + '-r'|'--rename') + COMPREPLY=( $(compgen -W "user_name" -- "$cur") ) + return 0 + ;; + '-u'|'--user') + COMPREPLY=( $(compgen -W "login" -- "$cur") ) + return 0 + ;; + '-d'|'--database') + COMPREPLY=( $(compgen -W "file" -- "$cur") ) + return 0 + ;; + '-h'|'--help'|'-V'|'--version') + return 0 + ;; + esac + case $cur in + -*) + OPTS=" + --before + --clear + --database + --help + --import + --rename + --service + --set + --time + --user + --version + " + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) + return 0 + ;; + esac + return 0 +} +complete -F _lastlog2_module lastlog2 diff --git a/configure.ac b/configure.ac index a03f3c4428..600ff2a811 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,13 @@ LIBUUID_LT_MINOR=3 LIBUUID_LT_MICRO=0 LIBUUID_VERSION_INFO=`expr $LIBUUID_LT_MAJOR + $LIBUUID_LT_MINOR`:$LIBUUID_LT_MICRO:$LIBUUID_LT_MINOR +dnl liblastlog2 version +LIBLASTLOG2_VERSION="$PACKAGE_VERSION_MAJOR.$PACKAGE_VERSION_MINOR.$PACKAGE_VERSION_RELEASE" +LIBLASTLOG2_LT_MAJOR=1 +LIBLASTLOG2_LT_MINOR=3 +LIBLASTLOG2_LT_MICRO=0 +LIBLASTLOG2_VERSION_INFO=`expr $LIBLASTLOG2_LT_MAJOR + $LIBLASTLOG2_LT_MINOR`:$LIBLASTLOG2_LT_MICRO:$LIBLASTLOG2_LT_MINOR + dnl libmount version LIBMOUNT_VERSION="$PACKAGE_VERSION_MAJOR.$PACKAGE_VERSION_MINOR.$PACKAGE_VERSION_RELEASE" LIBMOUNT_LT_MAJOR=1 @@ -383,6 +390,7 @@ AC_CHECK_HEADERS([ \ unistd.h \ utmp.h \ utmpx.h \ + sqlite3.h \ ]) # There is a collision in old kernel-headers. The both files mount.h and fs.h @@ -505,6 +513,7 @@ have_shadow_h=$ac_cv_header_shadow_h have_sys_signalfd_h=$ac_cv_header_sys_signalfd_h have_utmpx_h=$ac_cv_header_utmpx_h have_mntent_h=$ac_cv_header_mntent_h +have_sqlite3_h=$ac_cv_header_sqlite3_h AS_CASE([$linux_os:$have_linux_version_h], [yes:no], @@ -1211,6 +1220,32 @@ AS_IF([test "x$build_libuuid" = xyes], [ AC_DEFINE(HAVE_LIBUUID, 1, [Define to 1 if you have the -luuid.]) ]) +dnl +dnl liblastlog2 +dnl +AC_ARG_ENABLE([liblastlog2], + AS_HELP_STRING([--disable-liblastlog2], [do not build liblastlog2 and lastlog2 utilities]), + [enable_liblastlog2=no], [enable_liblastlog2=check] +) +AC_SUBST([LIBLASTLOG2_VERSION]) +AC_SUBST([LIBLASTLOG2_VERSION_INFO]) +AC_DEFINE_UNQUOTED([LIBLASTLOG2_VERSION], ["$LIBLASTLOG2_VERSION"], [liblastlog2 version string]) + +have_lastlog2=no +AS_IF([test "x$enable_liblastlog2" != xno], [ + AS_CASE([$enable_liblastlog2:$have_sqlite3_h], + [yes:no], + [AC_MSG_ERROR([lastlog2 selected but sqlite3.h not found])], + [check:no], + [AC_MSG_WARN([sqlite3.h not found, do not build with lastlog2 support])], + [*:yes], + [have_lastlog2=yes + AC_DEFINE(HAVE_LIBLASTLOG2, 1, [Define to 1 if you have the -llastlog2.])] + ) +]) +AM_CONDITIONAL([BUILD_LIBLASTLOG2], [test "x$have_lastlog2" = xyes]) +AM_CONDITIONAL([BUILD_LIBLASTLOG2_TESTS], [test "x$have_lastlog2" = xyes && test "x$enable_static" = xyes]) + dnl dnl libblkid dnl @@ -2594,6 +2629,18 @@ AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ]) +AC_ARG_WITH([tmpfilesdir], + AS_HELP_STRING([--with-tmpfilesdir=DIR], [directory for tmpfiles. See tmpfiles.d(5) for details]), + [], [ + AS_IF([test "x$have_systemd" = xyes], [ + PKG_CHECK_VAR([with_tmpfilesdir], [systemd], [tmpfilesdir], + [], + [with_tmpfilesdir=no]) + ]) +]) +AS_IF([test "x$with_tmpfilesdir" != "xno"], [ + AC_SUBST([tmpfilesdir], [$with_tmpfilesdir]) +]) AC_ARG_WITH([smack], AS_HELP_STRING([--with-smack], [build with SMACK support]), @@ -2937,8 +2984,10 @@ AC_MSG_RESULT([ Bash completions: ${with_bashcompletiondir} Systemd support: ${have_systemd} Systemd unitdir: ${with_systemdsystemunitdir} + tmpfilesdir: ${with_tmpfilesdir} libeconf support: ${have_econf} Btrfs support: ${have_btrfs} + lastlog2 support: ${have_lastlog2} Wide-char support: ${build_widechar} libcryptsetup support: ${have_cryptsetup} diff --git a/liblastlog2/COPYING b/liblastlog2/COPYING new file mode 100644 index 0000000000..df71a98655 --- /dev/null +++ b/liblastlog2/COPYING @@ -0,0 +1,24 @@ +BSD 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. diff --git a/liblastlog2/Makemodule.am b/liblastlog2/Makemodule.am new file mode 100644 index 0000000000..2f69f88a44 --- /dev/null +++ b/liblastlog2/Makemodule.am @@ -0,0 +1,10 @@ +if BUILD_LIBLASTLOG2 + +include liblastlog2/man/Makemodule.am +include liblastlog2/src/Makemodule.am + +pkgconfig_DATA += liblastlog2/lastlog2.pc +PATHFILES += liblastlog2/lastlog2.pc +EXTRA_DIST += liblastlog2/COPYING + +endif # BUILD_LIBLASTLOG2 diff --git a/liblastlog2/README.md b/liblastlog2/README.md new file mode 100644 index 0000000000..8bcfb39b65 --- /dev/null +++ b/liblastlog2/README.md @@ -0,0 +1,40 @@ +# lastlog2 + +**Y2038 safe version of lastlog** + +## Background + +`lastlog` reports the last login of a given user or of all users who did ever login on a system. + +The standard `/var/log/lastlog` implementation using `lastlog.h` from glibc uses a **32bit** **time_t** in `struct lastlog` 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 /var/log/lastlog on 64bit architectures](https://www.thkukuk.de/blog/Y2038_glibc_lastlog_64bit/)" + +Additional, `/var/log/lastlog` can become really huge if there are big UIDs in use on the system. Since it is a sparse file, this is normally not a problem, but depending on the filesystem or the tools used for backup, this can become a real problem. + +Since there are only few applications which really support `lastlog`, the data is also not always correct. + +## lastlog2 + +`lastlog2` tries to solve this problems: + +* It's using sqlite3 as database backend. +* Data is only collected via a PAM module, so that every tools can make use of it, without modifying existing packages. +* The output is as compatible as possible with the old lastlog implementation. +* The old `/var/log/lastlog` file can be imported into the new database. +* The size of the database depends on the amount of users, not how big the biggest UID is. + +**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. + +Besides the lastlog2 library in this directory there are additional parts like service definition, PAM module and applications: + +* `liblastlog2.so.0` contains all high level functions to manage the data (util-linux/liblastlog2). +* `pam_lastlog2.so` shows the last login of a user and stores the new login into the database (util-linux/pam_lastlog2). +* `lastlog2` will display the last logins for all users, who did ever login (util-linux/misc-utils). +* `lastlog2-import.service` a service which imports lastlog data into lastlog2 database (util-linux/misc-utils). +* `lastlog2.conf` configuration for tmpfiles.d. See tmpfiles.d(5) for details (util-linux/misc-utils). + +By default the database will be written as `/var/lib/lastlog/lastlog2.db`. + diff --git a/liblastlog2/lastlog2.pc.in b/liblastlog2/lastlog2.pc.in new file mode 100644 index 0000000000..a2b61dd8a8 --- /dev/null +++ b/liblastlog2/lastlog2.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@usrlib_execdir@ +includedir=@includedir@ + +Name: lastlog2 +Description: Y2038 safe version of lastlog +Version: @LIBLASTLOG2_VERSION@ +Requires: +Cflags: -I${includedir}/lastlog2 +Libs: -L${libdir} -llastlog2 diff --git a/liblastlog2/man/Makemodule.am b/liblastlog2/man/Makemodule.am new file mode 100644 index 0000000000..803bfb7865 --- /dev/null +++ b/liblastlog2/man/Makemodule.am @@ -0,0 +1,20 @@ + +MANPAGES += \ + liblastlog2/man/lastlog2.3 \ + liblastlog2/man/ll2_import_lastlog.3 \ + liblastlog2/man/ll2_read_all.3 \ + liblastlog2/man/ll2_read_entry.3 \ + liblastlog2/man/ll2_remove_entry.3 \ + liblastlog2/man/ll2_rename_user.3 \ + liblastlog2/man/ll2_update_login_time.3 \ + liblastlog2/man/ll2_write_entry.3 + +dist_noinst_DATA += \ + liblastlog2/man/lastlog2.3.adoc \ + liblastlog2/man/ll2_import_lastlog.3.adoc \ + liblastlog2/man/ll2_read_all.3.adoc \ + liblastlog2/man/ll2_read_entry.3.adoc \ + liblastlog2/man/ll2_remove_entry.3.adoc \ + liblastlog2/man/ll2_rename_user.3.adoc \ + liblastlog2/man/ll2_update_login_time.3.adoc \ + liblastlog2/man/ll2_write_entry.3.adoc diff --git a/liblastlog2/man/lastlog2.3.adoc b/liblastlog2/man/lastlog2.3.adoc new file mode 100644 index 0000000000..05cab12187 --- /dev/null +++ b/liblastlog2/man/lastlog2.3.adoc @@ -0,0 +1,51 @@ +//po4a: entry man manual += lastlog2(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +lastlog2 - Y2038 safe version of lastlog library. + +== SYNOPSIS + +*#include * + +== DESCRIPTION + +*lastlog2* reports the last login of a given user or of all users who did ever login on a system. + +It's using sqlite3 as database backend. Data is only collected via a PAM module, so that every +tools can make use of it, without modifying existing packages. +The output is as compatible as possible with the old lastlog implementation. +By default the database will be written as `/var/lib/lastlog/lastlog2.db`. +The size of the database depends on the amount of users, not how big the biggest UID is. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_write_entry*(3), +*ll2_read_all*(3), +*ll2_read_entry*(3), +*ll2_update_login_time*(3), +*ll2_remove_entry*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_import_lastlog.3.adoc b/liblastlog2/man/ll2_import_lastlog.3.adoc new file mode 100644 index 0000000000..9ddf54b167 --- /dev/null +++ b/liblastlog2/man/ll2_import_lastlog.3.adoc @@ -0,0 +1,65 @@ +//po4a: entry man manual += ll2_import_lastlog(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_import_lastlog - Import old lastlog file. + +== SYNOPSIS + +*#include * + +*int ll2_import_lastlog (struct ll2_context *__context__, + const char *__lastlog_file__, + char **__error__);* + +== DESCRIPTION + +Importing all entries from _lastlog_file_ file (lastlog(8)) into +lastlog2 database defined with _context_. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +-------------------------------------- +char *error = NULL; +const char *lastlog_path = "/var/log/lastlog"; + +int ret = ll2_import_lastlog (NULL, lastlog_path, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_update_login_time*(3), +*ll2_rename_user*(3), +*ll2_remove_entry*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_new_context.3.adoc b/liblastlog2/man/ll2_new_context.3.adoc new file mode 100644 index 0000000000..9a46646a7d --- /dev/null +++ b/liblastlog2/man/ll2_new_context.3.adoc @@ -0,0 +1,61 @@ +//po4a: entry man manual += ll2_new_context(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_new_context - Context which defines the lastlog2 environment. + +== SYNOPSIS + +*#include * + +*ll2_context * ll2_new_context(const char *__db_path__); + +== DESCRIPTION + +Defining lastlog2 context e.g. database file, which will be used for +any other lastlog2 calls. If _db_path_ is NULL, the default path defined +in _LL2_DEFAULT_DATABASE_ will be taken. + +-------------------------------------- + +const char *db_path = "/var/lib/lastlog/lastlog2.db"; +ll2_context *context = ll2_new_context(db_path); + +-------------------------------------- + +== RETURN VALUE + +Returns context which will used for all other lastlog2 library calls. +This context should be released with _ll2_unref_context_ when it is not +needed anymore. +Returns NULL on an error. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_update_login_time*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_read_all.3.adoc b/liblastlog2/man/ll2_read_all.3.adoc new file mode 100644 index 0000000000..fbe589d3e1 --- /dev/null +++ b/liblastlog2/man/ll2_read_all.3.adoc @@ -0,0 +1,74 @@ +//po4a: entry man manual += ll2_read_all(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_read_all - Reads all entries from database and calls the callback function for each entry. + +== SYNOPSIS + +*#include * +*int ll2_read_all (struct ll2_context *__context__, + int (*__callback__)(const char *__user__, int64_t __ll_time__, + const char *__tty__, const char *__rhost__, + const char *__pam_service__, const char *__cb_error__), + char **__error__);* + +== DESCRIPTION + +Reads all entries from database, defined in _context_, and calls callback fuction _callback_ for each entry. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, will be taken. + +-------------------------------------- +char *error = NULL; +const char *user = "root"; + +static int +callback (const char *res_user, int64_t ll_time, const char *res_tty, + const char *res_rhost, const char *res_service, const char *cb_error) +{ + /* returning != 0 if no further entry has to be handled by the callback */ + return 0; +} + +int ret = ll2_read_all (NULL, callback, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. +If lastlog2 database does not exist at all, the errno ENOENT has been set +and can be checked. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_update_login_time*(3), +*ll2_remove_entry*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_read_entry.3.adoc b/liblastlog2/man/ll2_read_entry.3.adoc new file mode 100644 index 0000000000..3bef1a8378 --- /dev/null +++ b/liblastlog2/man/ll2_read_entry.3.adoc @@ -0,0 +1,72 @@ +//po4a: entry man manual += ll2_read_entry(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_read_entry - Reads one entry from database and returns that. + +== SYNOPSIS + +*#include * + +*int ll2_read_entry (struct ll2_context *__context__, const char *__user__, + int64_t *__ll_time__, char **__tty__, char **__rhost__, + char **__pam_service__, char **__error__);* + +== DESCRIPTION + +Reads the first entry from database, defined in _context_, for user _user_. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +-------------------------------------- +char *error = NULL; +const char *user = "root"; +int64_t res_time; +char *res_tty = NULL; +char *res_rhost = NULL; +char *res_service = NULL; + +int ret = ll2_read_entry (NULL, user, &res_time, &res_tty, &res_rhost, &res_service, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. +If lastlog2 database does not exist at all, the errno ENOENT has been set +and can be checked. + +The evaluated values are returned by _ll_time_, _tty_, _rhost_ and _pam_service_. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_update_login_time*(3), +*ll2_remove_entry*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_remove_entry.3.adoc b/liblastlog2/man/ll2_remove_entry.3.adoc new file mode 100644 index 0000000000..f38ab66b33 --- /dev/null +++ b/liblastlog2/man/ll2_remove_entry.3.adoc @@ -0,0 +1,63 @@ +//po4a: entry man manual += ll2_remove_entry(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_remove_entry - Remove all entries of an user. + +== SYNOPSIS + +*#include * + +*int ll2_remove_entry (struct ll2_context *__context__, const char *__user__, + char **__error__);* + +== DESCRIPTION + +Removing all database entries, defined in _context_, with the user name _user_. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +-------------------------------------- +char *error = NULL; +const char *user = "root"; + +int ret = ll2_remove_entry (NULL, user, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_update_login_time*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_rename_user.3.adoc b/liblastlog2/man/ll2_rename_user.3.adoc new file mode 100644 index 0000000000..2202fe2316 --- /dev/null +++ b/liblastlog2/man/ll2_rename_user.3.adoc @@ -0,0 +1,66 @@ +//po4a: entry man manual += ll2_rename_user(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_rename_user - Renames an user entry. + +== SYNOPSIS + +*#include * + +*int ll2_rename_user (struct ll2_context *__context__, const char *__user__, + const char *__newname__, char **__error__);* + +== DESCRIPTION + +Changing user name from _user_ to _newname_ of one entry in +database, which is defined by _context_. All other entries with the user _user_ +will be deleted. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +-------------------------------------- +char *error = NULL; +const char *user = "root"; +const char *new_user = "notroot"; + +int ret = ll2_rename_user (NULL, user, new_user, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_update_login_time*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_unref_context.3.adoc b/liblastlog2/man/ll2_unref_context.3.adoc new file mode 100644 index 0000000000..5e52ca60ef --- /dev/null +++ b/liblastlog2/man/ll2_unref_context.3.adoc @@ -0,0 +1,57 @@ +//po4a: entry man manual += ll2_unref_context(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_unref_context - Freeing lastlog2 context. + +== SYNOPSIS + +*#include * + +*context * ll2_unref_context(ll2_context *context); + +== DESCRIPTION + +Freeing lastlog2 context, which has been generated by +_ll2_new_context_. + +-------------------------------------- + +const char *db_path = "/var/lib/lastlog/lastlog2.db"; +ll2_context *context = ll2_new_context(db_path); + +/* other lastlog2 calls..... */ + +ll2_unref_context(context); + +-------------------------------------- + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_update_login_time*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_update_login_time.3.adoc b/liblastlog2/man/ll2_update_login_time.3.adoc new file mode 100644 index 0000000000..9cc43e164f --- /dev/null +++ b/liblastlog2/man/ll2_update_login_time.3.adoc @@ -0,0 +1,67 @@ +//po4a: entry man manual += ll2_update_login_time(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_update_login_time - Writes an *new* entry with updated login time. + +== SYNOPSIS + +*#include * + +*int ll2_update_login_time (struct ll2_context *__context__, + const char *__user__, int64_t __ll_time__, + char **__error__);* + +== DESCRIPTION + +Writes an *new* entry to database, defined in _context_, for user _user_. +Time is set by _ll_time_ whereas the other values are taken from +an already existing entry. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + +-------------------------------------- +char *error = NULL; +const char *user = "root"; +int64_t login_time = time(0); // Get the system time; + +int ret = ll2_update_login_time (NULL, user, login_time, &error); +-------------------------------------- + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string. It could also be NULL if the return value is -1. +_error_ should be freed by the caller. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_write_entry*(3), +*ll2_read_entry*(3), +*ll2_remove_entry*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/man/ll2_write_entry.3.adoc b/liblastlog2/man/ll2_write_entry.3.adoc new file mode 100644 index 0000000000..6dca1b5c66 --- /dev/null +++ b/liblastlog2/man/ll2_write_entry.3.adoc @@ -0,0 +1,70 @@ +//po4a: entry man manual += ll2_write_entry(3) +:doctype: manpage +:man manual: Programmer's Manual +:man source: util-linux {release-version} +:page-layout: base +:lib: liblastlog2 +:firstversion: 2.40 + +== NAME + +ll2_write_entry - Write a new entry into the database. + +== SYNOPSIS + +*#include * + +*int ll2_write_entry (struct ll2_context *__context__, const char *__user__, + int64_t __ll_time__, const char *__tty__, + const char *__rhost__, const char *__pam_service__, + char **__error__);* + +== DESCRIPTION + +Writes an new entry into database, which is defined in _context_. +If _context_ is NULL, the default database, defined in _LL2_DEFAULT_DATABASE_, +will be taken. + + +-------------------------------------- +time_t login_time = time(0); // Get the system time +char *error = NULL; +const char *user = "root"; + +int ret = ll2_write_entry (NULL, user, login_time, "pts/0", + "192.168.122.1", NULL, &error); +-------------------------------------- + +_pam_service_ is the service or instance name which has generated the entry (optional). + +== RETURN VALUE + +Returns 0 on success, -ENOMEM or -1 on other failure. +_error_ contains an error string if the return value is -1. +_error_ is not guaranteed to contain an error string, could also be NULL. +_error_ should be freed by the caller. + +== AUTHORS + +Thorsten Kukuk (kukuk@suse.de) + +== SEE ALSO + +*lastlog2*(3), +*ll2_new_context(3), +*ll2_unref_context(3), +*ll2_read_all*(3), +*ll2_read_entry*(3), +*ll2_update_login_time*(3), +*ll2_remove_entry*(3), +*ll2_rename_user*(3), +*ll2_import_lastlog*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/liblastlog2/meson.build b/liblastlog2/meson.build new file mode 100644 index 0000000000..c478939170 --- /dev/null +++ b/liblastlog2/meson.build @@ -0,0 +1,63 @@ +cc = meson.get_compiler('c') +pkg = import('pkgconfig') +dir_liblastlog2 = include_directories('src') +lib_lastlog2_sources = ''' + src/lastlog2.h + src/lastlog2P.h + src/lastlog2.c +'''.split() + +libsqlite3 = cc.find_library('sqlite3') + +liblastlog2_sym = 'src/liblastlog2.sym' +liblastlog2_sym_path = '@0@/@1@'.format(meson.current_source_dir(), liblastlog2_sym) + +lib_lastlog2 = both_libraries( + 'lastlog2', + lib_lastlog2_sources, + include_directories : [dir_include, dir_liblastlog2], + link_args : ['-Wl,--version-script=@0@'.format(liblastlog2_sym_path)], + link_depends : liblastlog2_sym, + dependencies : [libsqlite3], + install : build_liblastlog2, + version : liblastlog2_version, +) + +lastlog2_dep = declare_dependency(link_with: lib_lastlog2, include_directories: dir_liblastlog2) + +lastlog2_tests = [ + 'dlopen', + 'pam_lastlog2_output', + 'remove_entry', + 'rename_user', + 'write_read_user', + 'y2038_ll2_read_all', + 'y2038_sqlite3_time', +] +libdl = cc.find_library('dl') + +if build_liblastlog2 + pkg.generate( + lib_lastlog2, + description : 'library to manage last login data', + subdirs : 'lastlog2', + version : pc_version + ) + meson.override_dependency('lastlog2', lastlog2_dep) + + foreach lastlog2_test: lastlog2_tests + test_name = 'test_lastlog2_' + lastlog2_test + exe = executable( + test_name, + 'src/tests/tst_' + lastlog2_test + '.c', + include_directories : [dir_include, dir_liblastlog2], + link_with : [lib_common, lib_lastlog2], + dependencies : libdl + ) + # the test-setup expects the helpers in the toplevel build-directory + link = meson.project_build_root() / test_name + run_command('ln', '-srf', exe.full_path(), link, + check : true) + endforeach + +endif diff --git a/liblastlog2/src/Makemodule.am b/liblastlog2/src/Makemodule.am new file mode 100644 index 0000000000..712f086e98 --- /dev/null +++ b/liblastlog2/src/Makemodule.am @@ -0,0 +1,99 @@ +# includes +lastlog2incdir = $(includedir) +lastlog2inc_HEADERS = liblastlog2/src/lastlog2.h + +usrlib_exec_LTLIBRARIES += liblastlog2.la + +liblastlog2_la_SOURCES = \ + liblastlog2/src/lastlog2.c \ + liblastlog2/src/lastlog2P.h + +EXTRA_liblastlog2_la_DEPENDENCIES = \ + liblastlog2/src/liblastlog2.sym + +liblastlog2_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_liblastlog2_incdir) \ + -Iliblastlog2/src + +liblastlog2_la_LDFLAGS = $(SOLIB_LDFLAGS) +if HAVE_VSCRIPT +liblastlog2_la_LDFLAGS += liblastlog2_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/liblastlog2/src/liblastlog2.sym +endif +liblastlog2_la_LDFLAGS += -lsqlite3 +liblastlog2_la_LDFLAGS += -version-info $(LIBLASTLOG2_VERSION_INFO) + +if BUILD_LIBLASTLOG2_TESTS +check_PROGRAMS += \ + test_lastlog2_dlopen \ + test_lastlog2_pam_lastlog2_output \ + test_lastlog2_remove_entry \ + test_lastlog2_rename_user \ + test_lastlog2_write_read_user \ + test_lastlog2_y2038_ll2_read_all \ + test_lastlog2_y2038_sqlite3_time + +lastlog2_tests_cflags = -DTEST_PROGRAM $(liblastlog2_la_CFLAGS) +lastlog2_tests_ldflags = +lastlog2_tests_ldadd = $(LDADD) liblastlog2.la +lastlog2_tests_ldflags += -static -lsqlite3 + +test_lastlog2_dlopen_SOURCES = liblastlog2/src/tests/tst_dlopen.c +test_lastlog2_dlopen_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_dlopen_LDFLAGS = $(lastlog2_tests_ldflags) -ldl +test_lastlog2_dlopen_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_pam_lastlog2_output_SOURCES = liblastlog2/src/tests/tst_pam_lastlog2_output.c +test_lastlog2_pam_lastlog2_output_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_pam_lastlog2_output_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_pam_lastlog2_output_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_remove_entry_SOURCES = liblastlog2/src/tests/tst_remove_entry.c +test_lastlog2_remove_entry_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_remove_entry_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_remove_entry_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_rename_user_SOURCES = liblastlog2/src/tests/tst_rename_user.c +test_lastlog2_rename_user_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_rename_user_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_rename_user_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_write_read_user_SOURCES = liblastlog2/src/tests/tst_write_read_user.c +test_lastlog2_write_read_user_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_write_read_user_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_write_read_user_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_y2038_ll2_read_all_SOURCES = liblastlog2/src/tests/tst_y2038_ll2_read_all.c +test_lastlog2_y2038_ll2_read_all_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_y2038_ll2_read_all_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_y2038_ll2_read_all_LDADD = $(lastlog2_tests_ldadd) + +test_lastlog2_y2038_sqlite3_time_SOURCES = liblastlog2/src/tests/tst_y2038_sqlite3_time.c +test_lastlog2_y2038_sqlite3_time_CFLAGS = $(lastlog2_tests_cflags) +test_lastlog2_y2038_sqlite3_time_LDFLAGS = $(lastlog2_tests_ldflags) +test_lastlog2_y2038_sqlite3_time_LDADD = $(lastlog2_tests_ldadd) + +endif #BUILD_LIBLIBLASTLOG2_TESTS + + + +EXTRA_DIST += liblastlog2/src/liblastlog2.sym + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-liblastlog2: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/liblastlog2.so"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/liblastlog2.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/liblastlog2.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ + (cd $(DESTDIR)$(usrlib_execdir) && \ + rm -f liblastlog2.so && \ + $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name liblastlog2.so); \ + fi + +uninstall-hook-liblastlog2: + rm -f $(DESTDIR)$(libdir)/liblastlog2.so* + +INSTALL_EXEC_HOOKS += install-exec-hook-liblastlog2 +UNINSTALL_HOOKS += uninstall-hook-liblastlog2 diff --git a/liblastlog2/src/lastlog2.c b/liblastlog2/src/lastlog2.c new file mode 100644 index 0000000000..827bfe8663 --- /dev/null +++ b/liblastlog2/src/lastlog2.c @@ -0,0 +1,594 @@ +/* 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 "lastlog2P.h" +#include "strutils.h" + +/* Set the ll2 context/environment */ +/* Returns the context or NULL if an error has happened. */ +extern struct ll2_context * ll2_new_context(const char *db_path) +{ + struct ll2_context *context = (struct ll2_context *)malloc(sizeof(struct ll2_context)); + + if (context) { + if (db_path) { + if ((context->lastlog2_path = strdup(db_path)) == NULL) { + free(context); + context = NULL; + } + } else { + if ((context->lastlog2_path = strdup(LL2_DEFAULT_DATABASE)) == NULL) { + free(context); + context = NULL; + } + } + } + return context; +} + +/* Release ll2 context/environment */ +extern void ll2_unref_context(struct ll2_context *context) +{ + if (context) + free(context->lastlog2_path); + free(context); +} + +/* Returns 0 on success, -ENOMEM or -1 on other failure. */ +static int +open_database_ro(struct ll2_context *context, sqlite3 **db, char **error) +{ + int ret = 0; + char *path = LL2_DEFAULT_DATABASE; + + if (context && context->lastlog2_path) + path = context->lastlog2_path; + + if (sqlite3_open_v2(path, db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) { + ret = -1; + if (error) + if (asprintf(error, "Cannot open database (%s): %s", + path, sqlite3_errmsg(*db)) < 0) + ret = -ENOMEM; + + sqlite3_close(*db); + } + + return ret; +} + +/* Returns 0 on success, -ENOMEM or -1 on other failure. */ +static int +open_database_rw(struct ll2_context *context, sqlite3 **db, char **error) +{ + int ret = 0; + char *path = LL2_DEFAULT_DATABASE; + + if (context && context->lastlog2_path) + path = context->lastlog2_path; + + if (sqlite3_open(path, db) != SQLITE_OK) { + ret = -1; + if (error) + if (asprintf(error, "Cannot create/open database (%s): %s", + path, sqlite3_errmsg(*db)) < 0) + ret = -ENOMEM; + + sqlite3_close(*db); + } + + return ret; +} + +/* Reads one entry from database and returns that. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +static int +read_entry(sqlite3 *db, const char *user, + int64_t *ll_time, char **tty, char **rhost, + char **pam_service, char **error) +{ + int retval = 0; + sqlite3_stmt *res = NULL; + static const char *sql = "SELECT Name,Time,TTY,RemoteHost,Service FROM Lastlog2 WHERE Name = ?"; + + if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to execute statement: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_read_entry; + } + + if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create search query: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_read_entry; + } + + int step = sqlite3_step(res); + + if (step == SQLITE_ROW) { + const unsigned char *luser = sqlite3_column_text(res, 0); + const unsigned char *uc; + + if (strcmp((const char *)luser, user) != 0) { + retval = -1; + if (error) + if (asprintf(error, "Returned data is for %s, not %s", luser, user) < 0) + retval = -ENOMEM; + goto out_read_entry; + } + + if (ll_time) + *ll_time = sqlite3_column_int64(res, 1); + + if (tty) { + uc = sqlite3_column_text(res, 2); + if (uc != NULL && strlen((const char *)uc) > 0) + if ((*tty = strdup((const char *)uc)) == NULL) { + retval = -ENOMEM; + goto out_read_entry; + } + } + if (rhost) { + uc = sqlite3_column_text(res, 3); + if (uc != NULL && strlen((const char *)uc) > 0) + if ((*rhost = strdup((const char *)uc)) == NULL) { + retval = -ENOMEM; + goto out_read_entry; + } + } + if (pam_service) { + uc = sqlite3_column_text(res, 4); + if (uc != NULL && strlen((const char *)uc) > 0) + if ((*pam_service = strdup((const char *)uc)) == NULL) { + retval = -ENOMEM; + goto out_read_entry; + } + } + } else { + retval = -1; + if (error) + if (asprintf(error, "User '%s' not found (%d)", user, step) < 0) + retval = -ENOMEM; + } + +out_read_entry: + if (res) + sqlite3_finalize(res); + + return retval; +} + +/* reads 1 entry from database and returns that. Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_read_entry(struct ll2_context *context, const char *user, + int64_t *ll_time, char **tty, char **rhost, + char **pam_service, char **error) +{ + sqlite3 *db; + int retval; + + if ((retval = open_database_ro(context, &db, error)) != 0) + return retval; + + retval = read_entry(db, user, ll_time, tty, rhost, pam_service, error); + + sqlite3_close(db); + + return retval; +} + +/* Write a new entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +static int +write_entry(sqlite3 *db, const char *user, + int64_t ll_time, const char *tty, const char *rhost, + const char *pam_service, char **error) +{ + int retval = 0; + char *err_msg = NULL; + sqlite3_stmt *res = NULL; + static const char *sql_table = "CREATE TABLE IF NOT EXISTS Lastlog2(Name TEXT PRIMARY KEY, Time INTEGER, TTY TEXT, RemoteHost TEXT, Service TEXT);"; + static const char *sql_replace = "REPLACE INTO Lastlog2 VALUES(?,?,?,?,?);"; + + if (sqlite3_exec(db, sql_table, 0, 0, &err_msg) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "SQL error: %s", err_msg) < 0) + retval = -ENOMEM; + + sqlite3_free(err_msg); + goto out_ll2_read_entry; + } + + if (sqlite3_prepare_v2(db, sql_replace, -1, &res, 0) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to execute statement: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create replace statement for user: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + if (sqlite3_bind_int64(res, 2, ll_time) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create replace statement for ll_time: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + if (sqlite3_bind_text(res, 3, tty, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create replace statement for tty: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + if (sqlite3_bind_text(res, 4, rhost, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create replace statement for rhost: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + if (sqlite3_bind_text(res, 5, pam_service, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create replace statement for PAM service: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } + + int step = sqlite3_step(res); + + if (step != SQLITE_DONE) { + retval = -1; + if (error) + if (asprintf(error, "Delete statement did not return SQLITE_DONE: %d", + step) < 0) + retval = -ENOMEM; + goto out_ll2_read_entry; + } +out_ll2_read_entry: + if (res) + sqlite3_finalize(res); + + return retval; +} + +/* Write a new entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_write_entry(struct ll2_context *context, const char *user, + int64_t ll_time, const char *tty, const char *rhost, + const char *pam_service, char **error) +{ + sqlite3 *db; + int retval; + + if ((retval = open_database_rw(context, &db, error)) != 0) + return retval; + + retval = write_entry(db, user, ll_time, tty, rhost, pam_service, error); + + sqlite3_close(db); + + return retval; +} + +/* Write a new entry with updated login time. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_update_login_time(struct ll2_context *context, const char *user, + int64_t ll_time, char **error) +{ + sqlite3 *db; + int retval; + char *tty; + char *rhost; + char *pam_service; + + if ((retval = open_database_rw(context , &db, error)) != 0) + return retval; + + if ((retval = read_entry(db, user, 0, &tty, &rhost, &pam_service, error)) != 0) { + sqlite3_close(db); + return retval; + } + + retval = write_entry(db, user, ll_time, tty, rhost, pam_service, error); + + sqlite3_close(db); + + free(tty); + free(rhost); + free(pam_service); + + return retval; +} + + +typedef int (*callback_f)(const char *user, int64_t ll_time, + const char *tty, const char *rhost, + const char *pam_service, const char *cb_error); + +static int +callback(void *cb_func, __attribute__((unused)) int argc, char **argv, __attribute__((unused)) char **azColName) +{ + char *endptr; + callback_f print_entry = cb_func; + + errno = 0; + char *cb_error = NULL; + int64_t ll_time = strtoll(argv[1], &endptr, 10); + if ((errno == ERANGE && (ll_time == INT64_MAX || ll_time == INT64_MIN)) + || (endptr == argv[1]) || (*endptr != '\0')) + if (asprintf(&cb_error, "Invalid numeric time entry for '%s': '%s'\n", argv[0], argv[1]) < 0) + return -1; + + print_entry(argv[0], ll_time, argv[2], argv[3], argv[4], cb_error); + free(cb_error); + + return 0; +} + +/* Reads all entries from database and calls the callback function for each entry. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_read_all(struct ll2_context *context, + int (*cb_func)(const char *user, int64_t ll_time, + const char *tty, const char *rhost, + const char *pam_service, const char *cb_error), + char **error) +{ + sqlite3 *db; + char *err_msg = 0; + int retval = 0; + + if ((retval = open_database_ro(context, &db, error)) != 0) + return retval; + + static const char *sql = "SELECT Name,Time,TTY,RemoteHost,Service FROM Lastlog2 ORDER BY Name ASC"; + + if (sqlite3_exec(db, sql, callback, cb_func, &err_msg) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "SQL error: %s", err_msg) < 0) + retval = -ENOMEM; + + sqlite3_free(err_msg); + } + + sqlite3_close(db); + + return retval; +} + +/* Remove an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +static int +remove_entry(sqlite3 *db, const char *user, char **error) +{ + int retval = 0; + sqlite3_stmt *res = NULL; + static const char *sql = "DELETE FROM Lastlog2 WHERE Name = ?"; + + if (sqlite3_prepare_v2(db, sql, -1, &res, 0) != SQLITE_OK) { + if (error) + if (asprintf(error, "Failed to execute statement: %s", + sqlite3_errmsg(db)) < 0) + return -ENOMEM; + + return -1; + } + + if (sqlite3_bind_text(res, 1, user, -1, SQLITE_STATIC) != SQLITE_OK) { + retval = -1; + if (error) + if (asprintf(error, "Failed to create delete statement: %s", + sqlite3_errmsg(db)) < 0) + retval = -ENOMEM; + goto out_remove_entry; + } + + int step = sqlite3_step(res); + + if (step != SQLITE_DONE) { + retval = -1; + if (error) + if (asprintf(error, "Delete statement did not return SQLITE_DONE: %d", + step) < 0) + retval = -ENOMEM; + } +out_remove_entry: + if (res) + sqlite3_finalize(res); + + return retval; +} + +/* Remove an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_remove_entry(struct ll2_context *context, const char *user, + char **error) +{ + sqlite3 *db; + int retval; + + if ((retval = open_database_rw(context, &db, error)) != 0) + return retval; + + retval = remove_entry(db, user, error); + + sqlite3_close(db); + + return retval; +} + +/* Renames an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_rename_user(struct ll2_context *context, const char *user, + const char *newname, char **error) +{ + sqlite3 *db; + int64_t ll_time; + char *tty; + char *rhost; + char *pam_service; + int retval; + + if ((retval = open_database_rw(context, &db, error)) != 0) + return retval; + + if ((retval = read_entry(db, user, &ll_time, &tty, &rhost, &pam_service, error) != 0)) { + sqlite3_close(db); + return retval; + } + + if ((retval = write_entry(db, newname, ll_time, tty, rhost, pam_service, error) != 0)) { + sqlite3_close(db); + free(tty); + free(rhost); + return retval; + } + + retval = remove_entry(db, user, error); + + sqlite3_close(db); + + free(tty); + free(rhost); + free(pam_service); + + return retval; +} + +/* Import old lastlog file. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +int +ll2_import_lastlog(struct ll2_context *context, const char *lastlog_file, + char **error) +{ + const struct passwd *pw; + struct stat statll; + sqlite3 *db; + FILE *ll_fp; + int retval = 0; + + if ((retval = open_database_rw(context, &db, error)) != 0) + return retval; + + ll_fp = fopen(lastlog_file, "r"); + if (ll_fp == NULL) { + if (error) + if (asprintf(error, "Failed to open '%s': %s", + lastlog_file, strerror(errno)) < 0) + return -ENOMEM; + + return -1; + } + + + if (fstat (fileno(ll_fp), &statll) != 0) { + if (error) + if (asprintf(error, "Cannot get size of '%s': %s", + lastlog_file, strerror(errno)) < 0) + return -ENOMEM; + + return -1; + } + + setpwent(); + while ((pw = getpwent()) != NULL ) { + off_t offset; + struct lastlog ll; + + offset = (off_t) pw->pw_uid * sizeof (ll); + + if ((offset + (off_t)sizeof(ll)) <= statll.st_size) { + if (fseeko(ll_fp, offset, SEEK_SET) == -1) + continue; /* Ignore seek error */ + + if (fread(&ll, sizeof(ll), 1, ll_fp) != 1) { + retval = -1; + if (error) + if (asprintf(error, "Failed to get the entry for UID '%lu'", + (unsigned long int)pw->pw_uid) < 0) + retval = -ENOMEM; + goto out_import_lastlog; + } + + if (ll.ll_time != 0) { + int64_t ll_time; + char tty[sizeof(ll.ll_line) + 1]; + char rhost[sizeof(ll.ll_host) + 1]; + + ll_time = ll.ll_time; + mem2strcpy(tty, ll.ll_line, sizeof(ll.ll_line), sizeof(tty)); + mem2strcpy(rhost, ll.ll_host, sizeof(ll.ll_host), sizeof(rhost)); + + if ((retval = write_entry(db, pw->pw_name, ll_time, tty, + rhost, NULL, error)) != 0) + goto out_import_lastlog; + } + } + } +out_import_lastlog: + endpwent(); + sqlite3_close(db); + + return retval; +} diff --git a/liblastlog2/src/lastlog2.h b/liblastlog2/src/lastlog2.h new file mode 100644 index 0000000000..28ed096c55 --- /dev/null +++ b/liblastlog2/src/lastlog2.h @@ -0,0 +1,91 @@ +/* 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. +*/ + +#ifndef _LIBLASTLOG2_H +#define _LIBLASTLOG2_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LL2_DEFAULT_DATABASE "/var/lib/lastlog/lastlog2.db" + +#include + +struct ll2_context; + +/* Set the ll2 context/environment */ +/* Returns the context or NULL if an error has happened. */ +extern struct ll2_context * ll2_new_context(const char *db_path); + +/* Release ll2 context/environment */ +extern void ll2_unref_context(struct ll2_context *context); + +/* Writes a new entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_write_entry (struct ll2_context *context, const char *user, + int64_t ll_time, const char *tty, + const char *rhost, const char *pam_service, + char **error); + +/* Calling a defined function for each entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_read_all (struct ll2_context *context, + int (*callback)(const char *user, int64_t ll_time, + const char *tty, const char *rhost, + const char *pam_service, const char *cb_error), + char **error); + +/* Reads one entry from database and returns that. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_read_entry (struct ll2_context *context, const char *user, + int64_t *ll_time, char **tty, char **rhost, + char **pam_service, char **error); + +/* Write a new entry with updated login time. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_update_login_time (struct ll2_context *context, + const char *user, int64_t ll_time, + char **error); + +/* Remove an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_remove_entry (struct ll2_context *context, const char *user, + char **error); + +/* Renames an user entry. Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_rename_user (struct ll2_context *context, const char *user, + const char *newname, char **error); + + +/* Import old lastlog file. + Returns 0 on success, -ENOMEM or -1 on other failure. */ +extern int ll2_import_lastlog (struct ll2_context *context, + const char *lastlog_file, char **error); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBLASTLOG2_H */ diff --git a/liblastlog2/src/lastlog2P.h b/liblastlog2/src/lastlog2P.h new file mode 100644 index 0000000000..b39f660e2c --- /dev/null +++ b/liblastlog2/src/lastlog2P.h @@ -0,0 +1,37 @@ +/* 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. +*/ + +#ifndef _LIBLASTLOG2_P_H +#define _LIBLASTLOG2_P_H + +#include "lastlog2.h" + +struct ll2_context { + char *lastlog2_path; +}; + +#endif /* _LIBLASTLOG2_P_H */ diff --git a/liblastlog2/src/liblastlog2.sym b/liblastlog2/src/liblastlog2.sym new file mode 100644 index 0000000000..afaf3cf1ac --- /dev/null +++ b/liblastlog2/src/liblastlog2.sym @@ -0,0 +1,13 @@ +LIBLASTLOG2_1.0 { + global: + ll2_new_context; + ll2_unref_context; + ll2_read_all; + ll2_read_entry; + ll2_remove_entry; + ll2_rename_user; + ll2_write_entry; + ll2_update_login_time; + ll2_import_lastlog; + local: *; +}; diff --git a/liblastlog2/src/tests/tst_dlopen.c b/liblastlog2/src/tests/tst_dlopen.c new file mode 100644 index 0000000000..112564b024 --- /dev/null +++ b/liblastlog2/src/tests/tst_dlopen.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + + 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. +*/ + +#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; +} diff --git a/liblastlog2/src/tests/tst_pam_lastlog2_output.c b/liblastlog2/src/tests/tst_pam_lastlog2_output.c new file mode 100644 index 0000000000..1fd1eec6d3 --- /dev/null +++ b/liblastlog2/src/tests/tst_pam_lastlog2_output.c @@ -0,0 +1,115 @@ +/* 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: + Store defined data into the database, read it, create the time + string like pam_lastlog2 does, compare the result. +*/ + +#include +#include +#include +#include +#include + +#include "lastlog2P.h" + +const char *expected = "Last login: Mon Mar 13 07:13:41 UTC 2023 from 192.168.122.1 on pts/0"; +const time_t login_time = 1678691621; +int +main(void) +{ + const char *user = "root"; + struct ll2_context *context = ll2_new_context("pam_lastlog2-output.db"); + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *date = NULL; + char the_time[256]; + char *error = NULL; + char *output = NULL; + + if (ll2_write_entry (context, user, login_time, "pts/0", + "192.168.122.1", NULL, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "ll2_write_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + if (ll2_read_entry (context, user, &ll_time, &tty, &rhost, + NULL, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Unknown error reading database %s", context ? context->lastlog2_path : "NULL"); + ll2_unref_context(context); + return 1; + } + + if (ll_time) { + struct tm *tm, tm_buf; + /* this is necessary if you compile this on architectures with + a 32bit time_t type. */ + time_t t_time = ll_time; + + if ((tm = localtime_r (&t_time, &tm_buf)) != NULL) { + strftime (the_time, sizeof (the_time), + " %a %b %e %H:%M:%S %Z %Y", tm); + date = the_time; + } + } + + if (asprintf (&output, "Last login:%s%s%s%s%s", + date ? date : "", + rhost ? " from " : "", + rhost ? rhost : "", + tty ? " on " : "", + tty ? tty : "") < 0) { + fprintf (stderr, "Out of memory!\n"); + ll2_unref_context(context); + return 1; + } + + if (strcmp (output, expected) != 0) { + fprintf (stderr, "Output '%s'\n does not match '%s'\n", + output, expected); + ll2_unref_context(context); + return 1; + } + + ll2_unref_context(context); + free (output); + free (tty); + free (rhost); + + return 0; +} diff --git a/liblastlog2/src/tests/tst_remove_entry.c b/liblastlog2/src/tests/tst_remove_entry.c new file mode 100644 index 0000000000..39079c881d --- /dev/null +++ b/liblastlog2/src/tests/tst_remove_entry.c @@ -0,0 +1,88 @@ +/* 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 an entry, delete that entry, and try to read entry again. + Reading the entry should fail. +*/ + +#include +#include +#include +#include + +#include "lastlog2.h" + +int +main(void) +{ + const char *user = "user"; + struct ll2_context *context = ll2_new_context("tst-delete-user.db"); + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *service = NULL; + char *error = NULL; + + if (ll2_write_entry (context, user, time (NULL), "test-tty", + "localhost", "sshd", &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "ll2_write_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + if (ll2_remove_entry (context, user, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "ll2_remove_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + /* this needs to fail, as the old entry shouldn't exist anymore. */ + if (ll2_read_entry (context, user, &ll_time, &tty, &rhost, &service, &error) == 0) { + fprintf (stderr, "Reading old user from database did not fail!\n"); + fprintf (stderr, "ll_time=%lld, tty='%s', rhost='%s', service='%s'\n", + (long long int)ll_time, tty, rhost, service); + ll2_unref_context(context); + return 1; + } + + ll2_unref_context(context); + free (error); + free (tty); + free (rhost); + free (service); + + return 0; +} diff --git a/liblastlog2/src/tests/tst_rename_user.c b/liblastlog2/src/tests/tst_rename_user.c new file mode 100644 index 0000000000..a1788b56f5 --- /dev/null +++ b/liblastlog2/src/tests/tst_rename_user.c @@ -0,0 +1,112 @@ +/* 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 an entry, rename that entry, and try to read the old and + new entry again. Reading the old entry should fail. +*/ + +#include +#include +#include +#include + +#include "lastlog2P.h" + +int +main(void) +{ + const char *user = "user"; + const char *newname = "new"; + struct ll2_context *context = ll2_new_context("tst-rename-user.db"); + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *service = NULL; + char *error = NULL; + + if (ll2_write_entry (context, user, time (NULL), "test-tty", + "localhost", "test-service", &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } + else + fprintf (stderr, "ll2_write_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + if (ll2_rename_user (context, user, newname, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } + else + fprintf (stderr, "ll2_rename_user failed\n"); + ll2_unref_context(context); + return 1; + } + + /* this needs to fail, as the old entry shouldn't exist anymore. */ + if (ll2_read_entry (context, user, &ll_time, &tty, &rhost, + &service, &error) == 0) { + fprintf (stderr, "Reading old user from database did not fail!\n"); + fprintf (stderr, "ll_time=%lld, tty='%s', rhost='%s', service='%s'\n", + (long long int)ll_time, tty, rhost, service); + ll2_unref_context(context); + return 1; + } + + if (error) { + free (error); + error = NULL; + } + + if (ll2_read_entry (context, newname, &ll_time, &tty, &rhost, &service, + &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Unknown error reading database %s", context->lastlog2_path); + ll2_unref_context(context); + return 1; + } + + if (strcmp (tty, "test-tty") != 0 || strcmp (rhost, "localhost") != 0 || + strcmp (service, "test-service") != 0) { + fprintf (stderr, "New entry data does not match old entry data!\n"); + } + + ll2_unref_context(context); + free (tty); + free (rhost); + free (service); + + return 0; +} diff --git a/liblastlog2/src/tests/tst_write_read_user.c b/liblastlog2/src/tests/tst_write_read_user.c new file mode 100644 index 0000000000..dbf1db78ab --- /dev/null +++ b/liblastlog2/src/tests/tst_write_read_user.c @@ -0,0 +1,165 @@ +/* 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 an entry, rename that entry, and try to read the old and + new entry again. Reading the old entry should fail. +*/ + +#include +#include +#include +#include +#include + +#include "lastlog2P.h" + +static int +test_args (struct ll2_context *context, const char *user, int64_t ll_time, + const char *tty, const char *rhost, const char *service) +{ + char *error = NULL; + int64_t res_time; + char *res_tty = NULL; + char *res_rhost = NULL; + char *res_service = NULL; + + if (ll2_write_entry (context, user, ll_time, tty, rhost, service, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "ll2_write_entry failed\n"); + return 1; + } + + if (ll2_read_entry (context, user, &res_time, &res_tty, &res_rhost, &res_service, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Unknown error reading database %s", context->lastlog2_path); + return 1; + } + + if (ll_time != res_time) { + fprintf (stderr, "Wrong time: got %lld, expect %lld\n", + (long long int)res_time, (long long int)ll_time); + return 1; + } + + if ((tty == NULL && res_tty != NULL) || + (tty != NULL && res_tty == NULL) || + (tty != NULL && res_tty != NULL && strcmp (tty, res_tty) != 0)) { + fprintf (stderr, "Wrong tty: got %s, expect %s\n", tty, res_tty); + return 1; + } + + if ((rhost == NULL && res_rhost != NULL) || + (rhost != NULL && res_rhost == NULL) || + (rhost != NULL && res_rhost != NULL && strcmp (rhost, res_rhost) != 0)) { + fprintf (stderr, "Wrong rhost: got %s, expect %s\n", rhost, res_rhost); + return 1; + } + + if ((service == NULL && res_service != NULL) || + (service != NULL && res_service == NULL) || + (service != NULL && res_service != NULL && strcmp (service, res_service) != 0)) { + fprintf (stderr, "Wrong service: got %s, expect %s\n", service, res_service); + return 1; + } + + + free (res_tty); + free (res_rhost); + free (res_service); + + return 0; +} + +int +main(void) +{ + struct ll2_context *context = ll2_new_context("tst-write-read-user.db"); + char *error = NULL; + int64_t res_time; + char *res_tty = NULL; + char *res_rhost = NULL; + char *res_service = NULL; + + if (test_args (context, "user1", time (NULL), "test-tty", "localhost", "test") != 0) { + ll2_unref_context(context); + return 1; + } + if (test_args (context, "user2", 0, NULL, NULL, NULL) != 0) { + ll2_unref_context(context); + return 1; + } + if (test_args (context, "user3", time (NULL), NULL, NULL, NULL) != 0) { + ll2_unref_context(context); + return 1; + } + if (test_args (context, "user4", time (NULL), "test-tty", NULL, NULL) != 0) { + ll2_unref_context(context); + return 1; + } + if (test_args (context, "user5", time (NULL), NULL, "localhost", NULL) != 0) { + ll2_unref_context(context); + return 1; + } + + /* Checking errno if the db file does not exist */ + struct ll2_context *context_not_found = ll2_new_context("no_file"); + if (ll2_read_entry (context_not_found, "user", &res_time, &res_tty, &res_rhost, &res_service, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Couldn't read entries for all users\n"); + + if(errno) { + if (errno == ENOENT) + { + fprintf (stderr, "Returning the correct errno: %s\n", + strerror (errno)); + ll2_unref_context(context_not_found); + return 0; + } + fprintf (stderr, "errno: %s\n", + strerror (errno)); + } else { + fprintf (stderr, "errno: NULL\n"); + } + + ll2_unref_context(context_not_found); + ll2_unref_context(context); + return 1; + } + + ll2_unref_context(context); + return 0; +} diff --git a/liblastlog2/src/tests/tst_y2038_ll2_read_all.c b/liblastlog2/src/tests/tst_y2038_ll2_read_all.c new file mode 100644 index 0000000000..014ae9d2b2 --- /dev/null +++ b/liblastlog2/src/tests/tst_y2038_ll2_read_all.c @@ -0,0 +1,156 @@ +/* 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 an entry with an 3*INT32_MAX timestamp, store that, + read that via ll2_read_all callback again and make sure the + timestamp is correct. +*/ + +#include +#include +#include +#include +#include +#include + +#include "lastlog2P.h" + +#define BIG_TIME_VALUE ((int64_t)3*INT32_MAX) + +const char *user = "y2038"; +const char *on_tty = "pts/test"; +const char *rhost = NULL; +const char *service = "sshd"; + +static int +check_y2038 (const char *res_user, int64_t ll_time, const char *res_tty, + const char *res_rhost, const char *res_service, const char *error) +{ + + if (strcmp (user, res_user) != 0) { + fprintf (stderr, "write/read entry user mismatch: written: %s, got: %s\n", + user, res_user); + exit (1); + } + + if (ll_time != BIG_TIME_VALUE) { + fprintf (stderr, "write/read entry time mismatch: written: %lld, got: %lld\n", + (long long int)BIG_TIME_VALUE, (long long int)ll_time); + exit (1); + } + + if (strcmp (on_tty, res_tty) != 0) { + fprintf (stderr, "write/read entry tty mismatch: written: %s, got: %s\n", + on_tty, res_tty); + exit (1); + } + + if (rhost != NULL) { + fprintf (stderr, "write/read entry rhost mismatch: written: NULL, got: %s\n", + res_rhost); + exit (1); + } + + if (strcmp (service, res_service) != 0) { + fprintf (stderr, "write/read entry service mismatch: written: %s, got: %s\n", + service, res_service); + exit (1); + } + + if (error != NULL) { + fprintf (stderr, "got error: %s\n", + error); + exit (1); + } + + return 0; +} + +int +main(void) +{ + struct ll2_context *context = ll2_new_context("y2038-ll2_read_all.db"); + char *error = NULL; + + remove (context->lastlog2_path); + + printf ("Big time value is: %lld\n", (long long int)BIG_TIME_VALUE); + + if (ll2_write_entry (context, user, BIG_TIME_VALUE, on_tty, rhost, service, + &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } + else + fprintf (stderr, "ll2_write_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + if (ll2_read_all (context, check_y2038, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Couldn't read entries for all users\n"); + ll2_unref_context(context); + return 1; + } + + /* Checking errno if the db file does not exist */ + remove (context->lastlog2_path); + + if (ll2_read_all (context, check_y2038, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Couldn't read entries for all users\n"); + + if(errno) { + if (errno == ENOENT) + { + fprintf (stderr, "Returning the correct errno: %s\n", + strerror (errno)); + ll2_unref_context(context); + return 0; + } + fprintf (stderr, "errno: %s\n", + strerror (errno)); + } else { + fprintf (stderr, "errno: NULL\n"); + } + + ll2_unref_context(context); + return 1; + } + + ll2_unref_context(context); + return 0; +} diff --git a/liblastlog2/src/tests/tst_y2038_sqlite3_time.c b/liblastlog2/src/tests/tst_y2038_sqlite3_time.c new file mode 100644 index 0000000000..a296634584 --- /dev/null +++ b/liblastlog2/src/tests/tst_y2038_sqlite3_time.c @@ -0,0 +1,83 @@ +/* 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 an entry with an INT64_MAX-1000 timestamp, store that, + read that again and make sure the timestamp is correct. +*/ + +#include +#include +#include +#include + +#include "lastlog2P.h" + +#define BIG_TIME_VALUE (INT64_MAX - 1000) + +int +main(void) +{ + const char *user = "y2038"; + struct ll2_context *context = ll2_new_context("y2038-sqlite3-time.db"); + int64_t ll_time = 0; + char *error = NULL; + + printf ("Big time value is: %lld\n", (long long int)BIG_TIME_VALUE); + + if (ll2_write_entry (context, user, BIG_TIME_VALUE, NULL, NULL, + NULL, &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "ll2_write_entry failed\n"); + ll2_unref_context(context); + return 1; + } + + if (ll2_read_entry (context, user, &ll_time, NULL, NULL, NULL, + &error) != 0) { + if (error) { + fprintf (stderr, "%s\n", error); + free (error); + } else + fprintf (stderr, "Unknown error reading database %s", context->lastlog2_path); + ll2_unref_context(context); + return 1; + } + + if (ll_time != BIG_TIME_VALUE) { + fprintf (stderr, "write/read entry time mismatch: written: %lld, got: %lld\n", + (long long int)BIG_TIME_VALUE, (long long int)ll_time); + ll2_unref_context(context); + return 1; + } + + ll2_unref_context(context); + return 0; +} diff --git a/meson.build b/meson.build index 5007ceb2ab..a68a422ebc 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,7 @@ pkgconfig = import('pkgconfig') libblkid_version = '1.1.0' libblkid_date = '01-Jun-2021' libuuid_version = '1.3.0' +liblastlog2_version = '1.3.0' libmount_version = '1.1.0' libsmartcols_version = '1.1.0' libfdisk_version = '1.1.0' @@ -79,6 +80,10 @@ build_libuuid = not get_option('build-libuuid').disabled() conf.set('HAVE_LIBUUID', build_libuuid ? 1 : false) summary('libuuid', build_libuuid ? 'enabled' : 'disabled', section : 'components') +build_liblastlog2 = not get_option('build-liblastlog2').disabled() +conf.set('HAVE_LIBLASTLOG2', build_liblastlog2 ? 1 : false) +summary('liblastlog2', build_liblastlog2 ? 'enabled' : 'disabled', section : 'components') + have_mountfd_api = cc.sizeof('struct mount_attr', prefix : '#include ') > 0 conf.set('HAVE_STRUCT_MOUNT_ATTR', have_mountfd_api ? 1 : false) conf.set('HAVE_MOUNTFD_API', have_mountfd_api ? 1 : false) @@ -926,7 +931,9 @@ subdir('libblkid') subdir('libmount') subdir('libsmartcols') subdir('libuuid') +subdir('liblastlog2') subdir('libfdisk') +subdir('pam_lastlog2') subdir('login-utils') subdir('sys-utils') subdir('disk-utils') @@ -941,6 +948,7 @@ includes = [dir_include, dir_libmount, dir_libfdisk, dir_libuuid, + dir_liblastlog2, dir_sys_utils] exes = [] @@ -2595,6 +2603,28 @@ exes += exe manadocs += ['misc-utils/mcookie.1.adoc'] bashcompletions += ['mcookie'] +if build_liblastlog2 + exe = executable( + 'lastlog2', + lastlog2_sources, + include_directories : includes, + link_with : [lib_common, lib_lastlog2], + install_dir : usrbin_exec_dir, + install : true) + exes += exe + manadocs += ['misc-utils/lastlog2.8.adoc'] + bashcompletions += ['lastlog2'] + manadocs += ['liblastlog2/man/lastlog2.3.adoc', + 'liblastlog2/man/ll2_write_entry.3.adoc', + 'liblastlog2/man/ll2_read_entry.3.adoc', + 'liblastlog2/man/ll2_import_lastlog.3.adoc', + 'liblastlog2/man/ll2_read_all.3.adoc', + 'liblastlog2/man/ll2_remove_entry.3.adoc', + 'liblastlog2/man/ll2_rename_user.3.adoc', + 'liblastlog2/man/ll2_update_login_time.3.adoc' + ] +endif + exe = executable( 'namei', namei_sources, diff --git a/meson_options.txt b/meson_options.txt index 5555f50d4b..b3cdc2dd3e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -37,6 +37,8 @@ option('build-libblkid', type : 'feature', description : 'build libblkid and many related utilities') option('build-libuuid', type : 'feature', description : 'build libuuid and uuid utilities') +option('build-liblastlog2', type : 'feature', + description : 'build liblastlog2 and lastlog2 utilities') option('build-libmount', type : 'feature', description : 'build libmount') option('build-libsmartcols', type : 'feature', @@ -214,3 +216,9 @@ option('fs-search-path-extra', option('vendordir', type: 'string', description : 'directory for distribution provided econf files') + +option('pamlibdir', type : 'string', + description : 'directory for PAM modules') +option('lastlog-compat-symlink', type : 'boolean', + value : 'false', + description : 'create lastlog compat symlink') diff --git a/misc-utils/.gitignore b/misc-utils/.gitignore index c0287917d9..24b40acafa 100644 --- a/misc-utils/.gitignore +++ b/misc-utils/.gitignore @@ -2,4 +2,6 @@ getopt.1 uuidd.8 uuidd.rc uuidd.service +lastlog2-import.service uuidd.socket +lastlog2.conf diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 7fbd667b1b..5f035f84de 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -100,6 +100,20 @@ lsblk_LDADD += -ludev endif endif # BUILD_LSBLK +if BUILD_LIBLASTLOG2 +usrbin_exec_PROGRAMS += lastlog2 +MANPAGES += misc-utils/lastlog2.8 +dist_noinst_DATA += misc-utils/lastlog2.8.adoc +lastlog2_SOURCES = misc-utils/lastlog2.c lib/strutils.c +lastlog2_LDADD = $(LDADD) liblastlog2.la -lsqlite3 +lastlog2_CFLAGS = $(AM_CFLAGS) -I$(ul_liblastlog2_incdir) +systemdsystemunit_DATA += \ + misc-utils/lastlog2-import.service +tmpfiles_DATA += misc-utils/lastlog2.conf +endif +PATHFILES += misc-utils/lastlog2-import.service \ + misc-utils/lastlog2.conf + if BUILD_UUIDGEN usrbin_exec_PROGRAMS += uuidgen MANPAGES += misc-utils/uuidgen.1 diff --git a/misc-utils/lastlog2-import.service.in b/misc-utils/lastlog2-import.service.in new file mode 100644 index 0000000000..c57a897529 --- /dev/null +++ b/misc-utils/lastlog2-import.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Import lastlog data into lastlog2 database +Documentation=man:lastlog2(8) +After=local-fs.target +ConditionPathExists=/var/log/lastlog +ConditionPathExists=!/var/lib/lastlog/lastlog2.db + +[Service] +Type=oneshot +ExecStart=@usrbin_execdir@/lastlog2 --import /var/log/lastlog +ExecStartPost=/usr/bin/mv /var/log/lastlog /var/log/lastlog.migrated +RemainAfterExit=true + +[Install] +WantedBy=default.target diff --git a/misc-utils/lastlog2.8.adoc b/misc-utils/lastlog2.8.adoc new file mode 100644 index 0000000000..b6be372d6d --- /dev/null +++ b/misc-utils/lastlog2.8.adoc @@ -0,0 +1,96 @@ +//po4a: entry man manual +//// +Copyright 2023 Thorsten Kukuk (kukuk@suse.de) +This file may be copied under the terms of the GNU Public License. +//// += lastlog2(8) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: lastlog2 + +== NAME + +lastlog2 - display date of last login for all users or a specific one + +== SYNOPSIS + +*lastlog2* [options] + +== DESCRIPTION + + +*lastlog2* displays the content of the last login database. The _login-name_, +_last-login-time_, _tty_ and _remote-host_ will be printed. +The default (no flags) causes all last login entries to be printed, sorted +by the order as written the first time into the database. + +Compared to *lastlog* this command is Y2038 safe and uses sqlite3 to store the +information and not a sparse file. + +== OPTIONS + +*-b*, *--before* _DAYS_:: +Print only last login records older than _DAYS_. + +*-C*, *--clear*:: +Clear last login record of a user. This option can be used only together with +*-u' (*--user*). + +*-d*, *--database _FILE_:: +Use _FILE_ as lastlog2 database. + +*-h*, *--help*:: +Display help message and exit. + +*-i*, *--import* _FILE_:: +Import data from old lastlog file _FILE_. Existing entries in the lastlog2 +database will be overwritten. + +*-r*, *--rename* _NEWNAME_:: +This option can only be used together with *-u* (*--user*). + +*-s*, *--servive* _num_:: +Display PAM service used to login in the last column. + +*-S*, *--set*:: +Set last login record of a user to the current time. This option can only be used +together with *-u* (*--user*). + +*-t*, *--time* _DAYS_:: +Print only last login records more recent than _DAYS_. + +*-u*, *--users* _LOGINS_:: +Print only the last login record of the user _LOGIN_. + +*-v*, *--version*:: +Print version number and exit. + +If the user has never logged in the message **Never logged in** will be displayed +in the latest login time row. + +Only the entries for the current users of the system will be displayed. +Other entries may exist for users that were deleted previously. + +== FILES + +*/var/lib/lastlog/lastlog2.db*:: +Lastlog2 logging database file + + +== AUTHORS + +lastlog2 was written by Thorsten Kukuk for *liblastlog2*(3). + +== SEE ALSO + +*liblastlog2*(3) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/misc-utils/lastlog2.c b/misc-utils/lastlog2.c new file mode 100644 index 0000000000..010c7e6fa7 --- /dev/null +++ b/misc-utils/lastlog2.c @@ -0,0 +1,337 @@ +/* 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 "nls.h" +#include "c.h" +#include "strutils.h" +#include "lastlog2.h" + +static char *lastlog2_path = LL2_DEFAULT_DATABASE; + +static int bflg = 0; +static time_t b_days = 0; +static int tflg = 0; +static time_t t_days = 0; +static int sflg = 0; + +static int +print_entry(const char *user, int64_t ll_time, + const char *tty, const char *rhost, + const char *pam_service, const char *error) +{ + static int once = 0; + char *datep; + struct tm *tm, tm_buf; + char datetime[80]; + /* IPv6 address is at maximum 39 characters. + But for LL-addresses (fe80+only) the interface should be set, + so LL-address + % + IFNAMSIZ. */ + const int maxIPv6Addrlen = 42; + + /* Print only if older than b days */ + if (bflg && ((time (NULL) - ll_time) < b_days)) + return 0; + + /* Print only if newer than t days */ + if (tflg && ((time (NULL) - ll_time) > t_days)) + return 0; + /* this is necessary if you compile this on architectures with + a 32bit time_t type. */ + time_t t_time = ll_time; + tm = localtime_r (&t_time, &tm_buf); + if (tm == NULL) + datep = "(unknown)"; + else { + strftime (datetime, sizeof(datetime), "%a %b %e %H:%M:%S %z %Y", tm); + datep = datetime; + } + + if (ll_time == 0) + datep = "**Never logged in**"; + + if (!once) { + printf("Username Port From%*s Latest%*s%s\n", + maxIPv6Addrlen-4, " ", + sflg?(int)strlen(datep)-5:0, " ", sflg?"Service":""); + once = 1; + } + printf("%-16s %-8.8s %*s %s%*s%s\n", user, tty ? tty : "", + -maxIPv6Addrlen, rhost ? rhost : "", datep, + sflg?31-(int)strlen(datep):0, " ", sflg?(pam_service?pam_service:""):""); + + if (error) + printf("\nError: %s\n", error); + + return 0; +} + +static void +usage(void) +{ + FILE *output = stdout; + + fputs(USAGE_HEADER, output); + fprintf(output, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, output); + fputs(_(" -b, --before DAYS Print only records older than DAYS\n"), output); + fputs(_(" -C, --clear Clear record of a user (requires -u)\n"), output); + fputs(_(" -d, --database FILE Use FILE as lastlog2 database\n"), output); + fputs(_(" -i, --import FILE Import data from old lastlog file\n"), output); + fputs(_(" -r, --rename NEWNAME Rename existing user to NEWNAME (requires -u)\n"), output); + fputs(_(" -s, --service Display PAM service\n"), output); + fputs(_(" -S, --set ySet lastlog record to current time (requires -u)\n"), output); + fputs(_(" -t, --time DAYS Print only lastlog records more recent than DAYS\n"), output); + fputs(_(" -u, --user LOGIN Print lastlog record of the specified LOGIN\n"), output); + + fputs(USAGE_SEPARATOR, output); + fprintf(output, USAGE_HELP_OPTIONS(25)); + fprintf(output, USAGE_MAN_TAIL("lastlog2(8)")); + + exit(EXIT_SUCCESS); +} + +/* Check if an user exists on the system. + If yes, return 0, else return -1. */ +static int +check_user(const char *name) +{ + if (getpwnam(name) == NULL) + return -1; + return 0; +} + +int +main(int argc, char **argv) +{ + struct option const longopts[] = { + {"before", required_argument, NULL, 'b'}, + {"clear", no_argument, NULL, 'C'}, + {"database", required_argument, NULL, 'd'}, + {"help", no_argument, NULL, 'h'}, + {"import", required_argument, NULL, 'i'}, + {"rename", required_argument, NULL, 'r'}, + {"service", no_argument, NULL, 's'}, + {"set", no_argument, NULL, 'S'}, + {"time", required_argument, NULL, 't'}, + {"user", required_argument, NULL, 'u'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, '\0'} + }; + char *error = NULL; + int Cflg = 0; + int iflg = 0; + int rflg = 0; + int Sflg = 0; + int uflg = 0; + const char *user = NULL; + const char *newname = NULL; + const char *lastlog_file = NULL; + struct ll2_context *db_context = NULL; + + int c; + + while ((c = getopt_long(argc, argv, "b:Cd:hi:r:sSt:u:v", longopts, NULL)) != -1) { + switch (c) { + case 'b': /* before DAYS; Print only records older than DAYS */ + { + unsigned long days; + errno = 0; + days = strtoul_or_err(optarg, _("Cannot parse days")); + b_days = (time_t) days * (24L*3600L) /* seconds/DAY */; + bflg = 1; + } + break; + case 'C': /* clear; Clear record of a user (requires -u) */ + Cflg = 1; + break; + case 'd': /* database ; Use FILE as lastlog2 database */ + lastlog2_path = optarg; + break; + case 'h': /* help; Display this help message and exit */ + usage(); + break; + case 'i': /* import ; Import data from old lastlog file */ + lastlog_file = optarg; + iflg = 1; + break; + case 'r': /* rename ; Rename existing user to NEWNAME (requires -u) */ + rflg = 1; + newname = optarg; + break; + case 's': /* service; Display PAM service */ + sflg = 1; + break; + case 'S': /* set; Set lastlog record to current time (requires -u) */ + /* Set lastlog record of a user to the current time. */ + Sflg = 1; + break; + case 't': /* time ; Print only lastlog records more recent than DAYS */ + { + unsigned long days; + errno = 0; + days = strtoul_or_err(optarg, _("Cannot parse days")); + t_days = (time_t) days * (24L*3600L) /* seconds/DAY */; + tflg = 1; + } + break; + case 'u': /* user ; Print lastlog record of the specified LOGIN */ + uflg = 1; + user = optarg; + break; + case 'v': /* version; Print version number and exit */ + print_version(EXIT_SUCCESS); + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + + if ((Cflg + Sflg + iflg) > 1) { + errx(EXIT_FAILURE, _("Option -C, -i and -S cannot be used together\n")); + } + + db_context = ll2_new_context(lastlog2_path); + if (!db_context) + errx(EXIT_FAILURE, _("Couldn't initialize lastlog2 environment.\n")); + + if (iflg) { + /* Importing entries */ + if (ll2_import_lastlog(db_context, lastlog_file, &error) != 0) { + ll2_unref_context(db_context); + if (error) { + errx(EXIT_FAILURE, "%s\n", error); + } + else + errx(EXIT_FAILURE, _("Couldn't import entries from '%s'\n"), lastlog_file); + } + ll2_unref_context(db_context); + exit(EXIT_SUCCESS); + } + + if (Cflg || Sflg || rflg) { + /* udpating, inserting and removing entries */ + if (!uflg || strlen(user) == 0) { + ll2_unref_context(db_context); + errx(EXIT_FAILURE, _("Options -C, -r and -S require option -u to specify the user\n")); + } + + if ((Cflg || Sflg) && check_user(user) != 0) { + ll2_unref_context(db_context); + errx(EXIT_FAILURE, _("User '%s' does not exist.\n"), user); + } + + if (Cflg) { + if (ll2_remove_entry(db_context, user, &error) != 0) { + ll2_unref_context(db_context); + if (error) { + errx(EXIT_FAILURE, "%s\n", error); + } + else + errx(EXIT_FAILURE, _("Couldn't remove entry for '%s'\n"), user); + } + } + + if (Sflg) { + time_t ll_time = 0; + + if (time(&ll_time) == -1) { + ll2_unref_context(db_context); + errx(EXIT_FAILURE, _("Could not determine current time: %s"), + strerror(errno)); + } + + if (ll2_update_login_time(db_context, user, ll_time, &error) != 0) { + ll2_unref_context(db_context); + if (error) { + errx(EXIT_FAILURE, "%s\n", error); + } + else + errx(EXIT_FAILURE, _("Couldn't update login time for '%s'\n"), user); + } + + } + + if (rflg) { + if (ll2_rename_user(db_context, user, newname, &error) != 0) { + ll2_unref_context(db_context); + if (error) { + errx(EXIT_FAILURE, "%s\n", error); + } + else + errx(EXIT_FAILURE, _("Couldn't rename entry '%s' to '%s'\n"), user, newname); + } + } + + ll2_unref_context(db_context); + exit(EXIT_SUCCESS); + } + + if (user) { + /* print user specific information */ + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *service = NULL; + + if (check_user(user) != 0) { + ll2_unref_context(db_context); + errx(EXIT_FAILURE, _("User '%s' does not exist.\n"), user); + } + + /* We ignore errors, if the user is not in the database he did never login */ + ll2_read_entry(db_context, user, &ll_time, &tty, &rhost, + &service, NULL); + + print_entry(user, ll_time, tty, rhost, service, NULL); + + ll2_unref_context(db_context); + exit(EXIT_SUCCESS); + } + + /* print all information */ + if (ll2_read_all(db_context, print_entry, &error) != 0) { + ll2_unref_context(db_context); + if (error) { + errx(EXIT_FAILURE, "%s\n", error); + } + else + errx(EXIT_FAILURE, _("Couldn't read entries for all users\n")); + } + + ll2_unref_context(db_context); + exit(EXIT_SUCCESS); +} diff --git a/misc-utils/lastlog2.conf.in b/misc-utils/lastlog2.conf.in new file mode 100644 index 0000000000..9d119576b1 --- /dev/null +++ b/misc-utils/lastlog2.conf.in @@ -0,0 +1,5 @@ +# This file is part of lastlog2. +# +# See tmpfiles.d(5) for details +# +d /var/lib/lastlog 0755 - - - diff --git a/misc-utils/meson.build b/misc-utils/meson.build index f1a9346931..6a7dd8ceef 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -12,6 +12,11 @@ look_sources = files( 'look.c', ) +lastlog2_sources = files( + 'lastlog2.c', +) + \ + strutils_c + mcookie_sources = files( 'mcookie.c', ) + \ @@ -71,6 +76,26 @@ test_uuidd_sources = files( 'test_uuidd.c', ) +if build_liblastlog2 and systemd.found() + lastlog2_service = configure_file( + input : 'lastlog2-import.service.in', + output : 'lastlog2-import.service', + configuration : conf) + install_data( + lastlog2_service, + install_dir : systemdsystemunitdir) +endif + +if build_liblastlog2 + lastlog2_tmpfiles = configure_file( + input : 'lastlog2.conf.in', + output : 'lastlog2.conf', + configuration : conf) + install_data( + lastlog2_tmpfiles, + install_dir : '/usr/lib/tmpfiles.d') +endif + if build_uuidd and systemd.found() uuidd_service = configure_file( input : 'uuidd.service.in', diff --git a/pam_lastlog2/COPYING b/pam_lastlog2/COPYING new file mode 100644 index 0000000000..df71a98655 --- /dev/null +++ b/pam_lastlog2/COPYING @@ -0,0 +1,24 @@ +BSD 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. diff --git a/pam_lastlog2/Makemodule.am b/pam_lastlog2/Makemodule.am new file mode 100644 index 0000000000..6d0dc2656e --- /dev/null +++ b/pam_lastlog2/Makemodule.am @@ -0,0 +1,8 @@ +if BUILD_LIBLASTLOG2 + +include pam_lastlog2/man/Makemodule.am +include pam_lastlog2/src/Makemodule.am + +EXTRA_DIST += pam_lastlog2/COPYING + +endif # BUILD_LIBLASTLOG2 diff --git a/pam_lastlog2/man/Makemodule.am b/pam_lastlog2/man/Makemodule.am new file mode 100644 index 0000000000..3f19ea1c7d --- /dev/null +++ b/pam_lastlog2/man/Makemodule.am @@ -0,0 +1,6 @@ + +MANPAGES += \ + pam_lastlog2/man/pam_lastlog2.8 + +dist_noinst_DATA += \ + pam_lastlog2/man/pam_lastlog2.8.adoc diff --git a/pam_lastlog2/man/pam_lastlog2.8.adoc b/pam_lastlog2/man/pam_lastlog2.8.adoc new file mode 100644 index 0000000000..1ed5ba641b --- /dev/null +++ b/pam_lastlog2/man/pam_lastlog2.8.adoc @@ -0,0 +1,78 @@ +//po4a: entry man manual += pam_lastlog2(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:lib: pam_lastlog2 +:firstversion: 2.40 +:page-layout: base + +== NAME + +pam_lastlog2 - PAM module to display date of last login + +== SYNOPSIS + +*pam_lastlog2.so* [debug] [silent] [silent_if=] [database=] + +== DESCRIPTION + +pam_lastlog2 is a PAM module to display a line of information about the last login of the user. The module uses the /var/lib/lastlog/lastlog2.db database file to store all informations. + +Compared to pam_lastlog this PAM module is Y2038 safe and uses sqlite3 to store the information. + +== OPTIONS + +*debug*:: +Print debug information. + +*silent*:: +Avoid all messages except errors and don't inform the user about any previous login, only update the /var/lib/lastlog/lastlog2.db database. + +*silent_if=*:: +The argument *services* is a comma separated list of PAM services. If a service is listed here, the last login message will not be shown. + +*database=*:: +Use *file* instead of /var/lib/lastlog/lastlog2.db. + +== MODULE TYPES PROVIDED + +The *session* module type is provided for displaying the information about the last login and updating the lastlog file. + +== 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 optional pam_lastlog2.so silent_if=gdm,gdm-password + +It is up to the administrator to decide if the user can login (optional/required) when +pam_lastlog2 has returned an error. + +== AUTHOR + +pam_lastlog2 was written by Thorsten Kukuk . + +== SEE ALSO + +*liblastlog2*(3), *pam.conf*(5), *pam.d*(5), *pam*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer-lib.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/pam_lastlog2/meson.build b/pam_lastlog2/meson.build new file mode 100644 index 0000000000..e9864f3cd3 --- /dev/null +++ b/pam_lastlog2/meson.build @@ -0,0 +1,39 @@ +cc = meson.get_compiler('c') +pkg = import('pkgconfig') +lib_pam_lastlog2_sources = ''' + src/pam_lastlog2.c +'''.split() + +pamlibdir = get_option('pamlibdir') +if pamlibdir == '' + pamlibdir = get_option('libdir') / 'security' +endif + +if build_liblastlog2 + pam_lastlog2_sym = 'src/pam_lastlog2.sym' + pam_lastlog2_sym_path = '@0@/@1@'.format(meson.current_source_dir(), pam_lastlog2_sym) + + libpam = cc.find_library('pam') + + pam_lastlog2 = both_libraries( + 'pam_lastlog2', + lib_pam_lastlog2_sources, + name_prefix : '', + include_directories : [dir_include, dir_liblastlog2], + link_args : ['-Wl,--version-script=@0@'.format(pam_lastlog2_sym_path)], + link_depends : pam_lastlog2_sym, + link_with : [lib_lastlog2], + dependencies : [libpam], + install : build_liblastlog2, + install_dir : pamlibdir, + version : liblastlog2_version, + ) + manadocs += ['pam_lastlog2/man/pam_lastlog2.8.adoc'] + + pkg.generate( + pam_lastlog2, + description : 'pam library to manage last login data with lastlog2', + subdirs : 'lastlog2', + version : pc_version + ) +endif diff --git a/pam_lastlog2/src/Makemodule.am b/pam_lastlog2/src/Makemodule.am new file mode 100644 index 0000000000..988441676f --- /dev/null +++ b/pam_lastlog2/src/Makemodule.am @@ -0,0 +1,21 @@ +securelibdir = $(libdir)/security +securelib_LTLIBRARIES = pam_lastlog2.la + +pam_lastlog2_la_SOURCES = \ + pam_lastlog2/src/pam_lastlog2.c + +EXTRA_pam_lastlog2_la_DEPENDENCIES = \ + pam_lastlog2/src/pam_lastlog2.sym + +pam_lastlog2_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_liblastlog2_incdir) \ + -Iliblastlog2/src + +pam_lastlog2_la_LDFLAGS = $(SOLIB_LDFLAGS) -module -avoid-version -shared +if HAVE_VSCRIPT +pam_lastlog2_la_LDFLAGS += pam_lastlog2_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/pam_lastlog2/src/pam_lastlog2.sym +endif + +EXTRA_DIST += pam_lastlog2/src/pam_lastlog2.sym diff --git a/pam_lastlog2/src/pam_lastlog2.c b/pam_lastlog2/src/pam_lastlog2.c new file mode 100644 index 0000000000..2802f09b28 --- /dev/null +++ b/pam_lastlog2/src/pam_lastlog2.c @@ -0,0 +1,340 @@ +/* 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 "lastlog2.h" +#include "strutils.h" + +#define LASTLOG2_DEBUG 01 /* send info to syslog(3) */ +#define LASTLOG2_QUIET 02 /* keep quiet about things */ + +static const char *lastlog2_path = LL2_DEFAULT_DATABASE; + +/* 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 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 |= LASTLOG2_QUIET; + + /* step through arguments */ + for (; argc-- > 0; ++argv) { + if (strcmp (*argv, "debug") == 0) + ctrl |= LASTLOG2_DEBUG; + else if (strcmp (*argv, "silent") == 0) + ctrl |= LASTLOG2_QUIET; + else if ((str = startswith (*argv, "database=")) != NULL) + lastlog2_path = str; + else if ((str = startswith (*argv, "silent_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 & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "silent_if='%s' contains '%s'", str, service); + ctrl |= LASTLOG2_QUIET; + } + } else + pam_syslog (pamh, LOG_ERR, "Unknown option: %s", *argv); + } + + return ctrl; +} + +static int +write_login_data (pam_handle_t *pamh, int ctrl, const char *user) +{ + const void *void_str; + const char *tty; + const char *rhost; + const char *pam_service; + const char *xdg_vtnr; + int xdg_vtnr_nr; + char tty_buf[8]; + time_t ll_time; + char *error = NULL; + int retval; + + void_str = NULL; + retval = pam_get_item (pamh, PAM_TTY, &void_str); + if (retval != PAM_SUCCESS || void_str == NULL) + tty = ""; + else + tty = void_str; + + /* strip leading "/dev/" from tty. */ + const char *str = startswith(tty, "/dev/"); + if (str != NULL) + tty = str; + + if (ctrl & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "tty=%s", tty); + + /* 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) { + 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 & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "tty(XDG_VTNR)=%s", tty); + } + } + + 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 & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_XDISPLAY)=%s", rhost); + } + } else { + rhost = void_str; + if (ctrl & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_RHOST)=%s", rhost); + } + + void_str = NULL; + if ((pam_get_item (pamh, PAM_SERVICE, &void_str) != PAM_SUCCESS) || + void_str == NULL) + pam_service = ""; + else + pam_service = void_str; + + if (time (&ll_time) < 0) + return PAM_SYSTEM_ERR; + + struct ll2_context *context = ll2_new_context(lastlog2_path); + if (ll2_write_entry (context, user, ll_time, tty, rhost, + pam_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", lastlog2_path); + ll2_unref_context(context); + return PAM_SYSTEM_ERR; + } + ll2_unref_context(context); + + return PAM_SUCCESS; +} + +static int +show_lastlogin (pam_handle_t *pamh, int ctrl, const char *user) +{ + int64_t ll_time = 0; + char *tty = NULL; + char *rhost = NULL; + char *service = NULL; + char *date = NULL; + char the_time[256]; + char *error = NULL; + int retval = PAM_SUCCESS; + + if (ctrl & LASTLOG2_QUIET) + return retval; + + struct ll2_context *context = ll2_new_context(lastlog2_path); + if (ll2_read_entry (context, user, &ll_time, &tty, &rhost, + &service, &error) != 0) { + if (errno == ENOENT) + { + /* DB file not found --> it is OK */ + ll2_unref_context(context); + return PAM_SUCCESS; + } + if (error) { + pam_syslog (pamh, LOG_ERR, "%s", error); + free (error); + } else + pam_syslog (pamh, LOG_ERR, "Unknown error reading database %s", lastlog2_path); + ll2_unref_context(context); + return PAM_SYSTEM_ERR; + } + ll2_unref_context(context); + + if (ll_time) { + struct tm *tm, tm_buf; + /* this is necessary if you compile this on architectures with + a 32bit time_t type. */ + time_t t_time = ll_time; + + if ((tm = localtime_r (&t_time, &tm_buf)) != NULL) { + strftime (the_time, sizeof (the_time), + " %a %b %e %H:%M:%S %Z %Y", tm); + date = the_time; + } + } + + if (date != NULL || rhost != NULL || tty != NULL) + retval = pam_info(pamh, "Last login:%s%s%s%s%s", + date ? date : "", + rhost ? " from " : "", + rhost ? rhost : "", + tty ? " on " : "", + tty ? tty : ""); + + _pam_drop(service); + _pam_drop(rhost); + _pam_drop(tty); + + return retval; +} + +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; +} + +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; + int ctrl; + + ctrl = _pam_parse_args (pamh, flags, argc, argv); + + 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 & LASTLOG2_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 & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "Couldn't find user %s", + (const char *)user); + return PAM_USER_UNKNOWN; + } + + if (ctrl & LASTLOG2_DEBUG) + pam_syslog (pamh, LOG_DEBUG, "user=%s", user); + + show_lastlogin (pamh, ctrl, user); + + return write_login_data (pamh, ctrl, user); +} + +int +pam_sm_close_session (pam_handle_t *pamh __attribute__((__unused__)), + int flags __attribute__((__unused__)), + int argc __attribute__((__unused__)), + const char **argv __attribute__((__unused__))) +{ + return PAM_SUCCESS; +} diff --git a/pam_lastlog2/src/pam_lastlog2.sym b/pam_lastlog2/src/pam_lastlog2.sym new file mode 100644 index 0000000000..1633155d73 --- /dev/null +++ b/pam_lastlog2/src/pam_lastlog2.sym @@ -0,0 +1,11 @@ + +{ + global: + pam_sm_acct_mgmt; + pam_sm_authenticate; + pam_sm_chauthtok; + pam_sm_close_session; + pam_sm_open_session; + pam_sm_setcred; + local: *; +}; \ No newline at end of file diff --git a/tests/commands.sh b/tests/commands.sh index adf9544945..6699f3d252 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -14,6 +14,13 @@ TS_HELPER_LIBFDISK_MKPART="${ts_helpersdir}sample-fdisk-mkpart" TS_HELPER_LIBMOUNT_CONTEXT="${ts_helpersdir}test_mount_context" TS_HELPER_LIBFDISK_MKPART_FULLSPEC="${ts_helpersdir}sample-fdisk-mkpart-fullspec" TS_HELPER_LIBFDISK_SCRIPT_FUZZ="${ts_helpersdir}test_fdisk_script_fuzz" +TS_HELPER_LIBLASTLOG2_DLOPEN="${ts_helpersdir}test_lastlog2_dlopen" +TS_HELPER_LIBLASTLOG2_PAM_LASTLOG2_OUTPUT="${ts_helpersdir}test_lastlog2_pam_lastlog2_output" +TS_HELPER_LIBLASTLOG2_REMOVE_ENTRY="${ts_helpersdir}test_lastlog2_remove_entry" +TS_HELPER_LIBLASTLOG2_RENAME_USER="${ts_helpersdir}test_lastlog2_rename_user" +TS_HELPER_LIBLASTLOG2_WRITE_READ_USER="${ts_helpersdir}test_lastlog2_write_read_user" +TS_HELPER_LIBLASTLOG2_Y2038_LL2_READ_ALL="${ts_helpersdir}test_lastlog2_y2038_ll2_read_all" +TS_HELPER_LIBLASTLOG2_Y2038_SQLITE2_TIME="${ts_helpersdir}test_lastlog2_y2038_sqlite3_time" TS_HELPER_LIBMOUNT_LOCK="${ts_helpersdir}test_mount_lock" TS_HELPER_LIBMOUNT_OPTSTR="${ts_helpersdir}test_mount_optstr" TS_HELPER_LIBMOUNT_OPTLIST="${ts_helpersdir}test_mount_optlist" diff --git a/tests/ts/liblastlog2/dlopen b/tests/ts/liblastlog2/dlopen new file mode 100755 index 0000000000..8ce4e175d6 --- /dev/null +++ b/tests/ts/liblastlog2/dlopen @@ -0,0 +1,23 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="dlopen" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +# incorrect warning: top_builddir is referenced but not assigned. +# shellcheck disable=SC2154 +if [ -e "$top_builddir/meson.conf" ]; then + #meson build + libpath=${top_builddir}/liblastlog2 +else + #automake/autoconf build + libpath=${top_builddir}/.libs +fi + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_DLOPEN + +$TS_HELPER_LIBLASTLOG2_DLOPEN ${libpath}/liblastlog2.so >/dev/null || ts_failed "returned an error" + +ts_finalize diff --git a/tests/ts/liblastlog2/pam_lastlog2_output b/tests/ts/liblastlog2/pam_lastlog2_output new file mode 100755 index 0000000000..c9961f1102 --- /dev/null +++ b/tests/ts/liblastlog2/pam_lastlog2_output @@ -0,0 +1,17 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="pam_lastlog2_output" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_PAM_LASTLOG2_OUTPUT + +export TZ=UTC + +$TS_HELPER_LIBLASTLOG2_PAM_LASTLOG2_OUTPUT || ts_failed "returned an error" + +rm pam_lastlog2-output.db + +ts_finalize diff --git a/tests/ts/liblastlog2/remove_entry b/tests/ts/liblastlog2/remove_entry new file mode 100755 index 0000000000..63503d9d56 --- /dev/null +++ b/tests/ts/liblastlog2/remove_entry @@ -0,0 +1,15 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="remove_entry" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_REMOVE_ENTRY + +$TS_HELPER_LIBLASTLOG2_REMOVE_ENTRY || ts_failed "returned an error" + +rm tst-delete-user.db + +ts_finalize diff --git a/tests/ts/liblastlog2/rename_user b/tests/ts/liblastlog2/rename_user new file mode 100755 index 0000000000..38f07636d1 --- /dev/null +++ b/tests/ts/liblastlog2/rename_user @@ -0,0 +1,15 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="rename_user" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_RENAME_USER + +$TS_HELPER_LIBLASTLOG2_RENAME_USER || ts_failed "returned an error" + +rm tst-rename-user.db + +ts_finalize diff --git a/tests/ts/liblastlog2/sqlite3_time b/tests/ts/liblastlog2/sqlite3_time new file mode 100755 index 0000000000..9ff27fe5eb --- /dev/null +++ b/tests/ts/liblastlog2/sqlite3_time @@ -0,0 +1,15 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="sqlite3_time" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_Y2038_SQLITE2_TIME + +$TS_HELPER_LIBLASTLOG2_Y2038_SQLITE2_TIME >/dev/null || ts_failed "returned an error" + +rm y2038-sqlite3-time.db + +ts_finalize diff --git a/tests/ts/liblastlog2/write_read_user b/tests/ts/liblastlog2/write_read_user new file mode 100755 index 0000000000..8a6028abc8 --- /dev/null +++ b/tests/ts/liblastlog2/write_read_user @@ -0,0 +1,15 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="write_read_user" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_WRITE_READ_USER + +$TS_HELPER_LIBLASTLOG2_WRITE_READ_USER || ts_failed "returned an error" + +rm tst-write-read-user.db + +ts_finalize diff --git a/tests/ts/liblastlog2/y2038_ll2_read_all b/tests/ts/liblastlog2/y2038_ll2_read_all new file mode 100755 index 0000000000..da779c298f --- /dev/null +++ b/tests/ts/liblastlog2/y2038_ll2_read_all @@ -0,0 +1,15 @@ +#!/bin/bash + +TS_TOPDIR="${0%/*}/../.." +TS_DESC="y2038_ll2_read_all" + +. "$TS_TOPDIR"/functions.sh +ts_init "$*" + +ts_check_test_command $TS_HELPER_LIBLASTLOG2_Y2038_LL2_READ_ALL + +$TS_HELPER_LIBLASTLOG2_Y2038_LL2_READ_ALL >/dev/null || ts_failed "returned an error" + +rm y2038-ll2_read_all.db + +ts_finalize -- 2.39.2