summaryrefslogtreecommitdiffstats
path: root/liblastlog2
diff options
context:
space:
mode:
Diffstat (limited to 'liblastlog2')
-rw-r--r--liblastlog2/COPYING24
-rw-r--r--liblastlog2/Makemodule.am10
-rw-r--r--liblastlog2/lastlog2.pc.in13
-rw-r--r--liblastlog2/man/Makemodule.am20
-rw-r--r--liblastlog2/man/lastlog2.367
-rw-r--r--liblastlog2/man/lastlog2.3.adoc51
-rw-r--r--liblastlog2/man/ll2_import_lastlog.384
-rw-r--r--liblastlog2/man/ll2_import_lastlog.3.adoc65
-rw-r--r--liblastlog2/man/ll2_read_all.393
-rw-r--r--liblastlog2/man/ll2_read_all.3.adoc74
-rw-r--r--liblastlog2/man/ll2_read_entry.391
-rw-r--r--liblastlog2/man/ll2_read_entry.3.adoc72
-rw-r--r--liblastlog2/man/ll2_remove_entry.382
-rw-r--r--liblastlog2/man/ll2_remove_entry.3.adoc63
-rw-r--r--liblastlog2/man/ll2_rename_user.385
-rw-r--r--liblastlog2/man/ll2_rename_user.3.adoc66
-rw-r--r--liblastlog2/man/ll2_update_login_time.386
-rw-r--r--liblastlog2/man/ll2_update_login_time.3.adoc67
-rw-r--r--liblastlog2/man/ll2_write_entry.388
-rw-r--r--liblastlog2/man/ll2_write_entry.3.adoc70
-rw-r--r--liblastlog2/meson.build63
-rw-r--r--liblastlog2/src/Makemodule.am99
-rw-r--r--liblastlog2/src/lastlog2.c595
-rw-r--r--liblastlog2/src/lastlog2.h91
-rw-r--r--liblastlog2/src/lastlog2P.h37
-rw-r--r--liblastlog2/src/liblastlog2.sym13
-rw-r--r--liblastlog2/src/tests/tst_dlopen.c40
-rw-r--r--liblastlog2/src/tests/tst_pam_lastlog2_output.c115
-rw-r--r--liblastlog2/src/tests/tst_remove_entry.c88
-rw-r--r--liblastlog2/src/tests/tst_rename_user.c112
-rw-r--r--liblastlog2/src/tests/tst_write_read_user.c165
-rw-r--r--liblastlog2/src/tests/tst_y2038_ll2_read_all.c156
-rw-r--r--liblastlog2/src/tests/tst_y2038_sqlite3_time.c83
33 files changed, 2928 insertions, 0 deletions
diff --git a/liblastlog2/COPYING b/liblastlog2/COPYING
new file mode 100644
index 0000000..df71a98
--- /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 0000000..2f69f88
--- /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/lastlog2.pc.in b/liblastlog2/lastlog2.pc.in
new file mode 100644
index 0000000..39df5da
--- /dev/null
+++ b/liblastlog2/lastlog2.pc.in
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: lastlog2
+Description: Y2038 safe version of lastlog
+Version: @LIBLASTLOG2_VERSION@
+Requires.private: sqlite3
+Cflags: -I${includedir}/liblastlog2
+Libs: -L${libdir} -llastlog2
diff --git a/liblastlog2/man/Makemodule.am b/liblastlog2/man/Makemodule.am
new file mode 100644
index 0000000..803bfb7
--- /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 b/liblastlog2/man/lastlog2.3
new file mode 100644
index 0000000..b2ac0d7
--- /dev/null
+++ b/liblastlog2/man/lastlog2.3
@@ -0,0 +1,67 @@
+'\" t
+.\" Title: lastlog2
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LASTLOG2" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+lastlog2 \- Y2038 safe version of lastlog library.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.SH "DESCRIPTION"
+.sp
+\fBlastlog2\fP reports the last login of a given user or of all users who did ever login on a system.
+.sp
+It\(cqs 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 \f(CR/var/lib/lastlog/lastlog2.db\fP.
+The size of the database depends on the amount of users, not how big the biggest UID is.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_write_entry\fP(3),
+\fBll2_read_all\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/lastlog2.3.adoc b/liblastlog2/man/lastlog2.3.adoc
new file mode 100644
index 0000000..05cab12
--- /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 <lastlog2.h>*
+
+== 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 b/liblastlog2/man/ll2_import_lastlog.3
new file mode 100644
index 0000000..23231c4
--- /dev/null
+++ b/liblastlog2/man/ll2_import_lastlog.3
@@ -0,0 +1,84 @@
+'\" t
+.\" Title: ll2_import_lastlog
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_IMPORT_LASTLOG" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_import_lastlog \- Import old lastlog file.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_import_lastlog (struct ll2_context *\fIcontext\fP,
+const char *\fIlastlog_file\fP,
+char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Importing all entries from \fIlastlog_file\fP file (lastlog(8)) into
+lastlog2 database defined with \fIcontext\fP.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+char *error = NULL;
+const char *lastlog_path = "/var/log/lastlog";
+
+int ret = ll2_import_lastlog (NULL, lastlog_path, &error);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_write_entry\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_remove_entry\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_import_lastlog.3.adoc b/liblastlog2/man/ll2_import_lastlog.3.adoc
new file mode 100644
index 0000000..9ddf54b
--- /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 <lastlog2.h>*
+
+*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_read_all.3 b/liblastlog2/man/ll2_read_all.3
new file mode 100644
index 0000000..753749f
--- /dev/null
+++ b/liblastlog2/man/ll2_read_all.3
@@ -0,0 +1,93 @@
+'\" t
+.\" Title: ll2_read_all
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_READ_ALL" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_read_all \- Reads all entries from database and calls the callback function for each entry.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+\fBint ll2_read_all (struct ll2_context *\fIcontext\fP,
+int (*\fIcallback\fP)(const char *\fIuser\fP, int64_t \fIll_time\fP,
+const char *\fItty\fP, const char *\fIrhost\fP,
+const char *\fIpam_service\fP, const char *\fIcb_error\fP),
+char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Reads all entries from database, defined in \fIcontext\fP, and calls callback fuction \fIcallback\fP for each entry.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP, will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+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);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+If lastlog2 database does not exist at all, the errno ENOENT has been set
+and can be checked.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_write_entry\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_read_all.3.adoc b/liblastlog2/man/ll2_read_all.3.adoc
new file mode 100644
index 0000000..fbe589d
--- /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 <lastlog2.h>*
+*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 b/liblastlog2/man/ll2_read_entry.3
new file mode 100644
index 0000000..53f1e26
--- /dev/null
+++ b/liblastlog2/man/ll2_read_entry.3
@@ -0,0 +1,91 @@
+'\" t
+.\" Title: ll2_read_entry
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_READ_ENTRY" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_read_entry \- Reads one entry from database and returns that.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_read_entry (struct ll2_context *\fIcontext\fP, const char *\fIuser\fP,
+int64_t *\fIll_time\fP, char \fB\fItty\fP, char \fP\fIrhost\fP,
+char \fB\fIpam_service\fP, char \fP\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Reads the first entry from database, defined in \fIcontext\fP, for user \fIuser\fP.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+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);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+If lastlog2 database does not exist at all, the errno ENOENT has been set
+and can be checked.
+.sp
+The evaluated values are returned by \fIll_time\fP, \fItty\fP, \fIrhost\fP and \fIpam_service\fP.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_write_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_read_entry.3.adoc b/liblastlog2/man/ll2_read_entry.3.adoc
new file mode 100644
index 0000000..3bef1a8
--- /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 <lastlog2.h>*
+
+*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 b/liblastlog2/man/ll2_remove_entry.3
new file mode 100644
index 0000000..3cb444b
--- /dev/null
+++ b/liblastlog2/man/ll2_remove_entry.3
@@ -0,0 +1,82 @@
+'\" t
+.\" Title: ll2_remove_entry
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_REMOVE_ENTRY" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_remove_entry \- Remove all entries of an user.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_remove_entry (struct ll2_context *\fIcontext\fP, const char *\fIuser\fP,
+char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Removing all database entries, defined in \fIcontext\fP, with the user name \fIuser\fP.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+char *error = NULL;
+const char *user = "root";
+
+int ret = ll2_remove_entry (NULL, user, &error);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_write_entry\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_remove_entry.3.adoc b/liblastlog2/man/ll2_remove_entry.3.adoc
new file mode 100644
index 0000000..f38ab66
--- /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 <lastlog2.h>*
+
+*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 b/liblastlog2/man/ll2_rename_user.3
new file mode 100644
index 0000000..7644838
--- /dev/null
+++ b/liblastlog2/man/ll2_rename_user.3
@@ -0,0 +1,85 @@
+'\" t
+.\" Title: ll2_rename_user
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_RENAME_USER" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_rename_user \- Renames an user entry.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_rename_user (struct ll2_context *\fIcontext\fP, const char *\fIuser\fP,
+const char *\fInewname\fP, char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Changing user name from \fIuser\fP to \fInewname\fP of one entry in
+database, which is defined by \fIcontext\fP. All other entries with the user \fIuser\fP
+will be deleted.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+char *error = NULL;
+const char *user = "root";
+const char *new_user = "notroot";
+
+int ret = ll2_rename_user (NULL, user, new_user, &error);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_write_entry\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_rename_user.3.adoc b/liblastlog2/man/ll2_rename_user.3.adoc
new file mode 100644
index 0000000..2202fe2
--- /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 <lastlog2.h>*
+
+*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_update_login_time.3 b/liblastlog2/man/ll2_update_login_time.3
new file mode 100644
index 0000000..ce08bc4
--- /dev/null
+++ b/liblastlog2/man/ll2_update_login_time.3
@@ -0,0 +1,86 @@
+'\" t
+.\" Title: ll2_update_login_time
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_UPDATE_LOGIN_TIME" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_update_login_time \- Writes an *new* entry with updated login time.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_update_login_time (struct ll2_context *\fIcontext\fP,
+const char *\fIuser\fP, int64_t \fIll_time\fP,
+char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Writes an \fBnew\fP entry to database, defined in \fIcontext\fP, for user \fIuser\fP.
+Time is set by \fIll_time\fP whereas the other values are taken from
+an already existing entry.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+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);
+.fam
+.fi
+.if n .RE
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string. It could also be NULL if the return value is \-1.
+\fIerror\fP should be freed by the caller.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_write_entry\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
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 0000000..9cc43e1
--- /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 <lastlog2.h>*
+
+*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 b/liblastlog2/man/ll2_write_entry.3
new file mode 100644
index 0000000..f16294a
--- /dev/null
+++ b/liblastlog2/man/ll2_write_entry.3
@@ -0,0 +1,88 @@
+'\" t
+.\" Title: ll2_write_entry
+.\" Author: [see the "AUTHOR(S)" section]
+.\" Generator: Asciidoctor 2.0.20
+.\" Date: 2024-03-20
+.\" Manual: Programmer's Manual
+.\" Source: util-linux 2.40
+.\" Language: English
+.\"
+.TH "LL2_WRITE_ENTRY" "3" "2024-03-20" "util\-linux 2.40" "Programmer\*(Aqs Manual"
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.ss \n[.ss] 0
+.nh
+.ad l
+.de URL
+\fI\\$2\fP <\\$1>\\$3
+..
+.als MTO URL
+.if \n[.g] \{\
+. mso www.tmac
+. am URL
+. ad l
+. .
+. am MTO
+. ad l
+. .
+. LINKSTYLE blue R < >
+.\}
+.SH "NAME"
+ll2_write_entry \- Write a new entry into the database.
+.SH "SYNOPSIS"
+.sp
+\fB#include <lastlog2.h>\fP
+.sp
+\fBint ll2_write_entry (struct ll2_context *\fIcontext\fP, const char *\fIuser\fP,
+int64_t \fIll_time\fP, const char *\fItty\fP,
+const char *\fIrhost\fP, const char *\fIpam_service\fP,
+char **\fIerror\fP);\fP
+.SH "DESCRIPTION"
+.sp
+Writes an new entry into database, which is defined in \fIcontext\fP.
+If \fIcontext\fP is NULL, the default database, defined in \fILL2_DEFAULT_DATABASE\fP,
+will be taken.
+.sp
+.if n .RS 4
+.nf
+.fam C
+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);
+.fam
+.fi
+.if n .RE
+.sp
+\fIpam_service\fP is the service or instance name which has generated the entry (optional).
+.SH "RETURN VALUE"
+.sp
+Returns 0 on success, \-ENOMEM or \-1 on other failure.
+\fIerror\fP contains an error string if the return value is \-1.
+\fIerror\fP is not guaranteed to contain an error string, could also be NULL.
+\fIerror\fP should be freed by the caller.
+.SH "AUTHORS"
+.sp
+Thorsten Kukuk (\c
+.MTO "kukuk\(atsuse.de" "" ")"
+.SH "SEE ALSO"
+.sp
+\fBlastlog2\fP(3),
+\fBll2_new_context(3),
+*ll2_unref_context(3),
+*ll2_read_all\fP(3),
+\fBll2_read_entry\fP(3),
+\fBll2_update_login_time\fP(3),
+\fBll2_remove_entry\fP(3),
+\fBll2_rename_user\fP(3),
+\fBll2_import_lastlog\fP(3)
+.SH "REPORTING BUGS"
+.sp
+For bug reports, use the issue tracker at \c
+.URL "https://github.com/util\-linux/util\-linux/issues" "" "."
+.SH "AVAILABILITY"
+.sp
+The \fBliblastlog2\fP library is part of the util\-linux package since version 2.40. It can be downloaded from \c
+.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file
diff --git a/liblastlog2/man/ll2_write_entry.3.adoc b/liblastlog2/man/ll2_write_entry.3.adoc
new file mode 100644
index 0000000..6dca1b5
--- /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 <lastlog2.h>*
+
+*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 0000000..6f8db53
--- /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()
+
+liblastlog2_sym = 'src/liblastlog2.sym'
+liblastlog2_sym_path = '@0@/@1@'.format(meson.current_source_dir(), liblastlog2_sym)
+
+if build_liblastlog2
+ lib_lastlog2 = both_libraries(
+ 'lastlog2',
+ lib_lastlog2_sources,
+ include_directories : [dir_include],
+ link_args : ['-Wl,--version-script=@0@'.format(liblastlog2_sym_path)],
+ link_depends : liblastlog2_sym,
+ dependencies : [lib_sqlite3],
+ 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')
+
+ pkg.generate(
+ lib_lastlog2,
+ description : 'library to manage last login data',
+ subdirs : 'lastlog2',
+ version : pc_version
+ )
+ meson.override_dependency('lastlog2', lastlog2_dep)
+
+ install_headers('src/lastlog2.h', subdir : 'liblastlog2')
+
+ 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],
+ link_with : [lib_common],
+ dependencies : [libdl, lastlog2_dep],
+ )
+ # 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 0000000..04b8e97
--- /dev/null
+++ b/liblastlog2/src/Makemodule.am
@@ -0,0 +1,99 @@
+# includes
+lastlog2incdir = $(includedir)/liblastlog2
+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_LIBADD = $(SQLITE3_LIBS)
+
+liblastlog2_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_liblastlog2_incdir) \
+ -I$(top_srcdir)/liblastlog2/src
+
+liblastlog2_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+liblastlog2_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/liblastlog2/src/liblastlog2.sym
+endif
+liblastlog2_la_LDFLAGS += -version-info $(LIBLASTLOG2_VERSION_INFO)
+
+EXTRA_DIST += liblastlog2/src/liblastlog2.sym
+
+
+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 = -static
+lastlog2_tests_ldadd = $(LDADD) liblastlog2.la $(SOLIB_LDFLAGS) $(SQLITE3_LIBS)
+
+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
+
+
+# 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 0000000..744d41f
--- /dev/null
+++ b/liblastlog2/src/lastlog2.c
@@ -0,0 +1,595 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+
+ Copyright (c) 2023, Thorsten Kukuk <kukuk@suse.com>
+
+ 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 <pwd.h>
+#include <errno.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sqlite3.h>
+#include <lastlog.h>
+
+#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 && asprintf(error, "Failed to open '%s': %s",
+ lastlog_file, strerror(errno)) < 0)
+ return -ENOMEM;
+
+ return -1;
+ }
+
+
+ if (fstat(fileno(ll_fp), &statll) != 0) {
+ retval = -1;
+ if (error && asprintf(error, "Cannot get size of '%s': %s",
+ lastlog_file, strerror(errno)) < 0)
+ retval = -ENOMEM;
+
+ goto done;
+ }
+
+ 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);
+done:
+ fclose(ll_fp);
+
+ return retval;
+}
diff --git a/liblastlog2/src/lastlog2.h b/liblastlog2/src/lastlog2.h
new file mode 100644
index 0000000..280f387
--- /dev/null
+++ b/liblastlog2/src/lastlog2.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+
+ Copyright (c) 2023, Thorsten Kukuk <kukuk@suse.com>
+
+ 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 _PATH_LOCALSTATEDIR "/lib/lastlog/lastlog2.db"
+
+#include <stdint.h>
+
+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 0000000..b39f660
--- /dev/null
+++ b/liblastlog2/src/lastlog2P.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: BSD-2-Clause
+
+ Copyright (c) 2024, Thorsten Kukuk <kukuk@suse.com>
+
+ 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 0000000..c21cced
--- /dev/null
+++ b/liblastlog2/src/liblastlog2.sym
@@ -0,0 +1,13 @@
+LIBLASTLOG2_2_40 {
+ 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 0000000..112564b
--- /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 <nalin@redhat.com> 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 <dlfcn.h>
+#include <stdio.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+/* 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 0000000..1fd1eec
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 0000000..39079c8
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 0000000..a1788b5
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 0000000..dbf1db7
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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 0000000..014ae9d
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#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 0000000..a296634
--- /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 <kukuk@suse.com>
+
+ 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#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;
+}