summaryrefslogtreecommitdiffstats
path: root/liblastlog2/src/lastlog2.c
diff options
context:
space:
mode:
Diffstat (limited to 'liblastlog2/src/lastlog2.c')
-rw-r--r--liblastlog2/src/lastlog2.c595
1 files changed, 595 insertions, 0 deletions
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;
+}