From ec813dffa59d30fa30ab3e4932095337b5813132 Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Fri, 15 Oct 2021 17:29:51 +0300 Subject: Prompt-driven auth. helper By the term "prompt-driven" I mean two-way conversation between the screensaver dialog and the PAM stack. As you probably know, PAM works by conversation with a dialog program asking the user to enter something in answer to each message it sends. In the most conventional case, the only question is "Password:" and the password is the only data the user enters. But in general, the number of questions and messages are not limited to that. The previous support of PAM helper (gs-auth-helper.c) was written and worked for the mentioned "only password" authentication scheme. For other schemes it wasn't enough. New implementation fixes that limitation. Same as the previous version of gs-auth-helper.c, the new version uses pipe interface for interprocess communication and synchronization. However, unlike the previous version, new version uses two pipes instead of a single pipe: the first one is used to transfer prompt text from PAM via the helper to the screensaver dialog, and the second one is used to transfer the user input from the dialog to helper (and then the helper replies with it back to PAM). Having that bidirectional prompt/reply channel it is possible to make as many prompt/reply passes as required by PAM. The present helper program (see the helper/ dir) is based on the helper written by okir@suse.de, which is in turn loosely based on unix_chkpwd by Andrew Morgan. All new code is untabified under the assumption the tab width is 8. Signed-off-by: Paul Wolneykien --- helper/Makefile.am | 27 +++++ helper/helper_proto.c | 167 +++++++++++++++++++++++++++++ helper/helper_proto.h | 53 +++++++++ helper/pam-helper.c | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 536 insertions(+) create mode 100644 helper/Makefile.am create mode 100644 helper/helper_proto.c create mode 100644 helper/helper_proto.h create mode 100644 helper/pam-helper.c (limited to 'helper') diff --git a/helper/Makefile.am b/helper/Makefile.am new file mode 100644 index 0000000..7a0ab0a --- /dev/null +++ b/helper/Makefile.am @@ -0,0 +1,27 @@ +## We require new-style dependency handling. +AUTOMAKE_OPTIONS = 1.7 + +noinst_LIBRARIES = libhelper-proto.a + +libhelper_proto_a_CFLAGS = \ + $(PAM_HELPER_CFLAGS) \ + -I$(top_srcdir)/src + +libhelper_proto_a_SOURCES = \ + helper_proto.h \ + helper_proto.c + +pkglibexec_PROGRAMS = mate-screensaver-pam-helper + +mate_screensaver_pam_helper_CFLAGS = \ + $(PAM_HELPER_CFLAGS) \ + -I$(top_srcdir)/src + +mate_screensaver_pam_helper_SOURCES = \ + pam-helper.c + +mate_screensaver_pam_helper_LDADD = \ + libhelper-proto.a + +mate_screensaver_pam_helper_LDFLAGS = \ + $(AUTH_LIBS) diff --git a/helper/helper_proto.c b/helper/helper_proto.c new file mode 100644 index 0000000..3abe263 --- /dev/null +++ b/helper/helper_proto.c @@ -0,0 +1,167 @@ +/* Part of mate-screensaver. + * + * Copyright (c) 2019-2021 Paul Wolneykien + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* Provides functions for two-way communication between the screensaver + * and the helper program. The idea of helper program is to be able to + * run mate-screensaver-dialog without any setuid bits. + */ + +#include "config.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "helper_proto.h" + +static ssize_t +read_all (int fd, void *buf, size_t count) +{ + ssize_t rd, t_rd = 0; + + if (0 == count) + return 0; + + while (t_rd < count) + { + rd = read (fd, buf + t_rd, count - t_rd); + if (0 == rd) + break; + if (rd < 0) + return rd; + t_rd += rd; + } + + return t_rd; +} + +ssize_t +read_msg (int fd, char *buf, size_t length) +{ + size_t msg_len; + ssize_t rd; + + rd = read_all (fd, &msg_len, sizeof msg_len); + if (rd < 0) + return HELPER_IO_ERR; + if (rd > 0 && rd != sizeof msg_len) + return HELPER_LENGTH_READ_ERR; + + if (msg_len >= length) + return HELPER_TOO_LONG_ERR; + + if (msg_len > 0) + { + rd = read_all (fd, buf, msg_len); + if (rd < 0) + return HELPER_IO_ERR; + if (rd != msg_len) + return HELPER_MSG_READ_ERR; + } + else + rd = 0; + buf[rd] = '\0'; + + return rd; +} + +int +read_prompt (int fd, char *buf, size_t *length) +{ + int msg_type, rd; + + rd = read_all (fd, &msg_type, sizeof msg_type); + if (0 == rd) + return 0; + if (rd < 0) + return HELPER_IO_ERR; + if (rd > 0 && rd != sizeof msg_type) + return HELPER_TYPE_READ_ERR; + + rd = read_msg (fd, buf, *length); + if (rd < 0) + return rd; + + *length = rd; + return msg_type; +} + +static ssize_t +write_all (int fd, const void *buf, size_t count) +{ + ssize_t wt, t_wt = 0; + + if (0 == count) + return 0; + + while (t_wt < count) + { + wt = write (fd, buf + t_wt, count - t_wt); + if (0 == wt) + break; + if (wt < 0) + return wt; + t_wt += wt; + } + + return t_wt; +} + +ssize_t +write_msg (int fd, const void *buf, size_t length) +{ + ssize_t wt; + + wt = write_all (fd, &length, sizeof length); + if (wt < 0) + return HELPER_IO_ERR; + if (wt > 0 && wt != sizeof length) + return HELPER_LENGTH_WRITE_ERR; + + if (length > 0) + { + wt = write_all (fd, buf, length); + if (wt < 0) + return HELPER_IO_ERR; + if (wt != length) + return HELPER_MSG_WRITE_ERR; + } + else + wt = 0; + + return wt; +} + +int +write_prompt (int fd, int msg_type, const void *buf, size_t length) +{ + ssize_t wt; + + wt = write_all (fd, &msg_type, sizeof msg_type); + if (wt < 0) + return HELPER_IO_ERR; + if (wt > 0 && wt != sizeof msg_type) + return HELPER_TYPE_WRITE_ERR; + + wt = write_msg (fd, buf, length); + + return wt; +} diff --git a/helper/helper_proto.h b/helper/helper_proto.h new file mode 100644 index 0000000..b815b52 --- /dev/null +++ b/helper/helper_proto.h @@ -0,0 +1,53 @@ +/* Part of mate-screensaver. + * + * Copyright (c) 2019-2021 Paul Wolneykien + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* Provides functions for two-way communication between the screensaver + * and the helper program. The idea of helper program is to be able to + * run mate-screensaver-dialog without any setuid bits. + */ + +#ifndef __HELPER_PROTO_H +#define __HELPER_PROTO_H + +#include "config.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif + +#define HELPER_IO_ERR -1 + +#define HELPER_LENGTH_READ_ERR -2 +#define HELPER_TOO_LONG_ERR -3 +#define HELPER_MSG_READ_ERR -4 +#define HELPER_TYPE_READ_ERR -5 + +ssize_t read_msg (int fd, char *buf, size_t length); +int read_prompt (int fd, char *buf, size_t *length); + +#define HELPER_LENGTH_WRITE_ERR -6 +#define HELPER_MSG_WRITE_ERR -7 +#define HELPER_TYPE_WRITE_ERR -8 + +ssize_t write_msg (int fd, const void *buf, size_t length); +int write_prompt (int fd, int msg_type, const void *buf, size_t length); + +#endif /* __HELPER_PROTO_H */ diff --git a/helper/pam-helper.c b/helper/pam-helper.c new file mode 100644 index 0000000..c2bd837 --- /dev/null +++ b/helper/pam-helper.c @@ -0,0 +1,289 @@ +/* Part of mate-screensaver. + * + * Copyright (C) 2002 SuSE Linux AG. + * Copyright (c) 2019-2021 Paul Wolneykien + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* + * Set*id helper program for PAM authentication. + * + * It is supposed to be called from mate-screensaver + * in order to communicate with Linux PAM as a privileged proxy. + * The conversation messages from the PAM stack is transmitted to + * mate-screensaver dialog via stdout and the received user replies + * read from stdin are sent back to PAM. + * + * Based on the helper written by okir@suse.de, loosely based on + * unix_chkpwd by Andrew Morgan. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helper_proto.h" +#include "gs-auth-pam.h" + +#define MAXLEN 1024 + +enum { + UNIX_PASSED = 0, + UNIX_FAILED = 1 +}; + +static char * program_name; + +/* + * Log error messages + */ +static void +_log_err(int err, const char *format,...) +{ + va_list args; + + va_start(args, format); + openlog(program_name, LOG_CONS | LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + +static void +su_sighandler(int sig) +{ + if (sig > 0) { + _log_err(LOG_NOTICE, "caught signal %d.", sig); + exit(sig); + } +} + +/* + * Setup signal handlers + */ +static void +setup_signals(void) +{ + struct sigaction action; + + memset((void *) &action, 0, sizeof(action)); + action.sa_handler = su_sighandler; + action.sa_flags = SA_RESETHAND; + sigaction(SIGILL, &action, NULL); + sigaction(SIGTRAP, &action, NULL); + sigaction(SIGBUS, &action, NULL); + sigaction(SIGSEGV, &action, NULL); + action.sa_handler = SIG_IGN; + action.sa_flags = 0; + sigaction(SIGTERM, &action, NULL); + sigaction(SIGHUP, &action, NULL); + sigaction(SIGINT, &action, NULL); + sigaction(SIGQUIT, &action, NULL); + sigaction(SIGALRM, &action, NULL); +} + +static int +_converse(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +{ + struct pam_response *reply = NULL; + char buf[MAXLEN]; + int num; + int ret = PAM_SUCCESS; + + if (!(reply = malloc(sizeof(*reply) * num_msg))) + return PAM_CONV_ERR; + + for (num = 0; num < num_msg; num++) { + ssize_t wt, rd; + size_t msg_len = strlen(msg[num]->msg); + wt = write_prompt (STDOUT_FILENO, + pam_style_to_gs_style (msg[num]->msg_style), + msg[num]->msg, msg_len); + if (wt < 0 || wt != msg_len) { + _log_err(LOG_ERR, "error writing promt"); + ret = PAM_CONV_ERR; + break; + } + + rd = read_msg (STDIN_FILENO, buf, sizeof (buf)); + if (rd < 0) { + _log_err(LOG_ERR, "error reading reply"); + ret = PAM_CONV_ERR; + break; + } + + reply[num].resp = malloc (rd + 1); + if (!reply[num].resp) + ret = PAM_BUF_ERR; + else { + reply[num].resp_retcode = 0; + memcpy (reply[num].resp, buf, rd); + reply[num].resp[rd] = '\0'; + } + } + + if (ret != PAM_SUCCESS && reply != NULL) { + for (num = 0; num < num_msg; num++) + free (reply[num].resp); + free (reply); + *resp = NULL; + } else + *resp = reply; + + return ret; +} + +static int +_authenticate(const char *service, const char *user) +{ + struct pam_conv conv = { _converse, NULL }; + pam_handle_t *pamh; + int err; + + err = pam_start(service, user, &conv, &pamh); + if (err != PAM_SUCCESS) { + _log_err(LOG_ERR, "pam_start(%s, %s) failed (errno %d)", + service, user, err); + return UNIX_FAILED; + } + + err = pam_authenticate(pamh, 0); + if (err != PAM_SUCCESS) + _log_err(LOG_ERR, "pam_authenticate(%s, %s): %s", + service, user, + pam_strerror(pamh, err)); + + if (err == PAM_SUCCESS) + { + int err2 = pam_setcred(pamh, PAM_REFRESH_CRED); + if (err2 != PAM_SUCCESS) + _log_err(LOG_ERR, "pam_setcred(%s, %s): %s", + service, user, + pam_strerror(pamh, err2)); + /* + * ignore errors on refresh credentials. + * If this did not work we use the old once. + */ + } + + pam_end(pamh, err); + + if (err != PAM_SUCCESS) + return UNIX_FAILED; + return UNIX_PASSED; +} + +static char * +getuidname(uid_t uid) +{ + struct passwd *pw; + static char username[32]; + + pw = getpwuid(uid); + if (pw == NULL) + return NULL; + + strncpy(username, pw->pw_name, sizeof(username)); + username[sizeof(username) - 1] = '\0'; + + endpwent(); + return username; +} + +int +main(int argc, char *argv[]) +{ + const char *program_name; + char *service, *user; + int fd; + uid_t uid; + + uid = getuid(); + + /* + * Make sure standard file descriptors are connected. + */ + while ((fd = open("/dev/null", O_RDWR)) <= 2) + ; + close(fd); + + /* + * Get the program name + */ + if ((program_name = strrchr(argv[0], '/')) != NULL) + program_name++; + else + program_name = argv[0]; + + /* + * Catch or ignore as many signal as possible. + */ + setup_signals(); + + /* + * Check argument list + */ + if (argc < 2 || argc > 3) { + _log_err(LOG_NOTICE, "Bad number of arguments (%d)", argc); + return UNIX_FAILED; + } + + /* + * Get the service name. + */ + service = argv[1]; + + /* + * Discourage users messing around (fat chance) + */ + if (isatty(STDIN_FILENO) && uid != 0) { + _log_err(LOG_NOTICE, + "Inappropriate use of Unix helper binary [UID=%d]", + uid); + fprintf(stderr, + "This binary is not designed for running in this way\n" + "-- the system administrator has been informed\n"); + sleep(10); /* this should discourage/annoy the user */ + return UNIX_FAILED; + } + + /* + * determine the caller's user name + */ + user = getuidname(uid); + if (argc == 3 && strcmp(user, argv[2])) { + user = argv[2]; + /* Discourage use of this program as a + * password cracker */ + if (uid != 0) + sleep(5); + } + return _authenticate(service, user); +} -- cgit v1.2.1