summaryrefslogtreecommitdiffstats
path: root/pam_lastlog2/src/pam_lastlog2.c
diff options
context:
space:
mode:
Diffstat (limited to 'pam_lastlog2/src/pam_lastlog2.c')
-rw-r--r--pam_lastlog2/src/pam_lastlog2.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/pam_lastlog2/src/pam_lastlog2.c b/pam_lastlog2/src/pam_lastlog2.c
new file mode 100644
index 0000000..e800700
--- /dev/null
+++ b/pam_lastlog2/src/pam_lastlog2.c
@@ -0,0 +1,341 @@
+/* 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 <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
+#include <security/_pam_macros.h>
+
+#include "lastlog2.h"
+#include "strutils.h"
+
+#define LASTLOG2_DEBUG 01 /* send info to syslog(3) */
+#define LASTLOG2_QUIET 02 /* keep quiet about things */
+
+static const char *lastlog2_path = LL2_DEFAULT_DATABASE;
+
+/* check for list match. */
+static int
+check_in_list (const char *service, const char *arg)
+{
+ const char *item;
+ const char *remaining;
+
+ if (!service)
+ return 0;
+
+ remaining = arg;
+
+ for (;;) {
+ item = strstr (remaining, service);
+ if (item == NULL)
+ break;
+
+ /* is it really the start of an item in the list? */
+ if (item == arg || *(item - 1) == ',') {
+ item += strlen (service);
+ /* is item really the service? */
+ if (*item == '\0' || *item == ',')
+ return 1;
+ }
+
+ remaining = strchr (item, ',');
+ if (remaining == NULL)
+ break;
+
+ /* skip ',' */
+ ++remaining;
+ }
+
+ return 0;
+}
+
+
+static int
+_pam_parse_args (pam_handle_t *pamh,
+ int flags, int argc,
+ const char **argv)
+{
+ int ctrl = 0;
+ const char *str;
+
+ /* does the application require quiet? */
+ if (flags & PAM_SILENT)
+ ctrl |= LASTLOG2_QUIET;
+
+ /* step through arguments */
+ for (; argc-- > 0; ++argv) {
+ if (strcmp (*argv, "debug") == 0)
+ ctrl |= LASTLOG2_DEBUG;
+ else if (strcmp (*argv, "silent") == 0)
+ ctrl |= LASTLOG2_QUIET;
+ else if ((str = startswith (*argv, "database=")) != NULL)
+ lastlog2_path = str;
+ else if ((str = startswith (*argv, "silent_if=")) != NULL) {
+ const void *void_str = NULL;
+ const char *service;
+ if ((pam_get_item (pamh, PAM_SERVICE, &void_str) != PAM_SUCCESS) ||
+ void_str == NULL)
+ service = "";
+ else
+ service = void_str;
+
+ if (check_in_list (service, str)) {
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "silent_if='%s' contains '%s'", str, service);
+ ctrl |= LASTLOG2_QUIET;
+ }
+ } else
+ pam_syslog (pamh, LOG_ERR, "Unknown option: %s", *argv);
+ }
+
+ return ctrl;
+}
+
+static int
+write_login_data (pam_handle_t *pamh, int ctrl, const char *user)
+{
+ const void *void_str;
+ const char *tty;
+ const char *rhost;
+ const char *pam_service;
+ const char *xdg_vtnr;
+ int xdg_vtnr_nr;
+ char tty_buf[8];
+ time_t ll_time;
+ char *error = NULL;
+ int retval;
+
+ void_str = NULL;
+ retval = pam_get_item (pamh, PAM_TTY, &void_str);
+ if (retval != PAM_SUCCESS || void_str == NULL)
+ tty = "";
+ else
+ tty = void_str;
+
+ /* strip leading "/dev/" from tty. */
+ const char *str = startswith(tty, "/dev/");
+ if (str != NULL)
+ tty = str;
+
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "tty=%s", tty);
+
+ /* if PAM_TTY is not set or an X11 $DISPLAY, try XDG_VTNR */
+ if ((tty[0] == '\0' || strchr(tty, ':') != NULL) && (xdg_vtnr = pam_getenv (pamh, "XDG_VTNR")) != NULL) {
+ xdg_vtnr_nr = atoi (xdg_vtnr);
+ if (xdg_vtnr_nr > 0 && snprintf (tty_buf, sizeof(tty_buf), "tty%d", xdg_vtnr_nr) < (int) sizeof(tty_buf)) {
+ tty = tty_buf;
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "tty(XDG_VTNR)=%s", tty);
+ }
+ }
+
+ void_str = NULL;
+ retval = pam_get_item (pamh, PAM_RHOST, &void_str);
+ if (retval != PAM_SUCCESS || void_str == NULL) {
+ void_str = NULL;
+ retval = pam_get_item (pamh, PAM_XDISPLAY, &void_str);
+ if (retval != PAM_SUCCESS || void_str == NULL) {
+ rhost = "";
+ } else {
+ rhost = void_str;
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_XDISPLAY)=%s", rhost);
+ }
+ } else {
+ rhost = void_str;
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "rhost(PAM_RHOST)=%s", rhost);
+ }
+
+ void_str = NULL;
+ if ((pam_get_item (pamh, PAM_SERVICE, &void_str) != PAM_SUCCESS) ||
+ void_str == NULL)
+ pam_service = "";
+ else
+ pam_service = void_str;
+
+ if (time (&ll_time) < 0)
+ return PAM_SYSTEM_ERR;
+
+ struct ll2_context *context = ll2_new_context(lastlog2_path);
+ if (ll2_write_entry (context, user, ll_time, tty, rhost,
+ pam_service, &error) != 0) {
+ if (error) {
+ pam_syslog (pamh, LOG_ERR, "%s", error);
+ free (error);
+ } else
+ pam_syslog (pamh, LOG_ERR, "Unknown error writing to database %s", lastlog2_path);
+ ll2_unref_context(context);
+ return PAM_SYSTEM_ERR;
+ }
+ ll2_unref_context(context);
+
+ return PAM_SUCCESS;
+}
+
+static int
+show_lastlogin (pam_handle_t *pamh, int ctrl, const char *user)
+{
+ int64_t ll_time = 0;
+ char *tty = NULL;
+ char *rhost = NULL;
+ char *service = NULL;
+ char *date = NULL;
+ char the_time[256];
+ char *error = NULL;
+ int retval = PAM_SUCCESS;
+
+ if (ctrl & LASTLOG2_QUIET)
+ return retval;
+
+ struct ll2_context *context = ll2_new_context(lastlog2_path);
+ if (ll2_read_entry (context, user, &ll_time, &tty, &rhost,
+ &service, &error) != 0) {
+ if (errno == ENOENT)
+ {
+ /* DB file not found --> it is OK */
+ ll2_unref_context(context);
+ free(error);
+ return PAM_SUCCESS;
+ }
+ if (error) {
+ pam_syslog (pamh, LOG_ERR, "%s", error);
+ free (error);
+ } else
+ pam_syslog (pamh, LOG_ERR, "Unknown error reading database %s", lastlog2_path);
+ ll2_unref_context(context);
+ return PAM_SYSTEM_ERR;
+ }
+ ll2_unref_context(context);
+
+ if (ll_time) {
+ struct tm *tm, tm_buf;
+ /* this is necessary if you compile this on architectures with
+ a 32bit time_t type. */
+ time_t t_time = ll_time;
+
+ if ((tm = localtime_r (&t_time, &tm_buf)) != NULL) {
+ strftime (the_time, sizeof (the_time),
+ " %a %b %e %H:%M:%S %Z %Y", tm);
+ date = the_time;
+ }
+ }
+
+ if (date != NULL || rhost != NULL || tty != NULL)
+ retval = pam_info(pamh, "Last login:%s%s%s%s%s",
+ date ? date : "",
+ rhost ? " from " : "",
+ rhost ? rhost : "",
+ tty ? " on " : "",
+ tty ? tty : "");
+
+ _pam_drop(service);
+ _pam_drop(rhost);
+ _pam_drop(tty);
+
+ return retval;
+}
+
+int
+pam_sm_authenticate (pam_handle_t *pamh __attribute__((__unused__)),
+ int flags __attribute__((__unused__)),
+ int argc __attribute__((__unused__)),
+ const char **argv __attribute__((__unused__)))
+{
+ return PAM_IGNORE;
+}
+
+int
+pam_sm_setcred (pam_handle_t *pamh __attribute__((__unused__)),
+ int flags __attribute__((__unused__)),
+ int argc __attribute__((__unused__)),
+ const char **argv __attribute__((__unused__)))
+{
+ return PAM_IGNORE;
+}
+
+int
+pam_sm_acct_mgmt (pam_handle_t *pamh __attribute__((__unused__)),
+ int flags __attribute__((__unused__)),
+ int argc __attribute__((__unused__)),
+ const char **argv __attribute__((__unused__)))
+{
+ return PAM_IGNORE;
+}
+
+int
+pam_sm_open_session (pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ const struct passwd *pwd;
+ const void *void_str;
+ const char *user;
+ int ctrl;
+
+ ctrl = _pam_parse_args (pamh, flags, argc, argv);
+
+ void_str = NULL;
+ int retval = pam_get_item (pamh, PAM_USER, &void_str);
+ if (retval != PAM_SUCCESS || void_str == NULL || strlen (void_str) == 0) {
+ if (!(ctrl & LASTLOG2_QUIET))
+ pam_syslog (pamh, LOG_NOTICE, "User unknown");
+ return PAM_USER_UNKNOWN;
+ }
+ user = void_str;
+
+ /* verify the user exists */
+ pwd = pam_modutil_getpwnam (pamh, user);
+ if (pwd == NULL) {
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "Couldn't find user %s",
+ (const char *)user);
+ return PAM_USER_UNKNOWN;
+ }
+
+ if (ctrl & LASTLOG2_DEBUG)
+ pam_syslog (pamh, LOG_DEBUG, "user=%s", user);
+
+ show_lastlogin (pamh, ctrl, user);
+
+ return write_login_data (pamh, ctrl, user);
+}
+
+int
+pam_sm_close_session (pam_handle_t *pamh __attribute__((__unused__)),
+ int flags __attribute__((__unused__)),
+ int argc __attribute__((__unused__)),
+ const char **argv __attribute__((__unused__)))
+{
+ return PAM_SUCCESS;
+}