]> git.armaanb.net Git - opendoas.git/blobdiff - pam.c
timestamp: error out if fstat and lstat st_ino and st_dev are not the same
[opendoas.git] / pam.c
diff --git a/pam.c b/pam.c
index 4f2731d5e133d12b664a63709c470b17d025a9be..c5a3001509b59364281466cca310d56ce523b990 100644 (file)
--- a/pam.c
+++ b/pam.c
  */
 
 #include <sys/types.h>
+#include <sys/wait.h>
+
 #include <err.h>
 #include <errno.h>
+#include <limits.h>
 #include <pwd.h>
+#ifdef HAVE_READPASSPHRASE_H
+#      include <readpassphrase.h>
+#else
+#      include "readpassphrase.h"
+#endif
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
 #include <unistd.h>
-#include <sys/wait.h>
-#include <signal.h>
-#ifdef __linux__
-#include <limits.h>
-#endif
 
 #include <security/pam_appl.h>
 
-#include "doas.h"
 #include "includes.h"
 
 #define PAM_SERVICE_NAME "doas"
 
 static pam_handle_t *pamh = NULL;
-static sig_atomic_t volatile caught_signal = 0;
 static char doas_prompt[128];
+static sig_atomic_t volatile caught_signal = 0;
 
 static void
 catchsig(int sig)
@@ -46,10 +50,10 @@ catchsig(int sig)
 }
 
 static char *
-prompt(const char *msg, int echo_on, int *pam)
+pamprompt(const char *msg, int echo_on, int *ret)
 {
        const char *prompt;
-       char *ret, buf[PAM_MAX_RESP_SIZE];
+       char *pass, buf[PAM_MAX_RESP_SIZE];
        int flags = RPP_REQUIRE_TTY | (echo_on ? RPP_ECHO_ON : RPP_ECHO_OFF);
 
        /* overwrite default prompt if it matches "Password:[ ]" */
@@ -59,32 +63,34 @@ prompt(const char *msg, int echo_on, int *pam)
        else
                prompt = msg;
 
-       ret = readpassphrase(prompt, buf, sizeof(buf), flags);
-       if (!ret)
-               *pam = PAM_CONV_ERR;
-       else if (!(ret = strdup(ret)))
-               *pam = PAM_BUF_ERR;
+       pass = readpassphrase(prompt, buf, sizeof(buf), flags);
+       if (!pass)
+               *ret = PAM_CONV_ERR;
+       else if (!(pass = strdup(pass)))
+               *ret = PAM_BUF_ERR;
+       else
+               *ret = PAM_SUCCESS;
 
        explicit_bzero(buf, sizeof(buf));
-       return ret;
+       return pass;
 }
 
 static int
-doas_pam_conv(int nmsgs, const struct pam_message **msgs,
+pamconv(int nmsgs, const struct pam_message **msgs,
                struct pam_response **rsps, __UNUSED void *ptr)
 {
        struct pam_response *rsp;
        int i, style;
-       int ret = PAM_SUCCESS;
+       int ret;
 
        if (!(rsp = calloc(nmsgs, sizeof(struct pam_response))))
-               errx(1, "couldn't malloc pam_response");
+               err(1, "could not allocate pam_response");
 
        for (i = 0; i < nmsgs; i++) {
                switch (style = msgs[i]->msg_style) {
                case PAM_PROMPT_ECHO_OFF:
                case PAM_PROMPT_ECHO_ON:
-                       rsp[i].resp = prompt(msgs[i]->msg, style == PAM_PROMPT_ECHO_ON, &ret);
+                       rsp[i].resp = pamprompt(msgs[i]->msg, style == PAM_PROMPT_ECHO_ON, &ret);
                        if (ret != PAM_SUCCESS)
                                goto fail;
                        break;
@@ -123,46 +129,144 @@ fail:
        return PAM_CONV_ERR;
 }
 
-int
-doas_pam(const char *user, const char* ruser, int interactive, int nopass)
+void
+pamcleanup(int ret, int sess, int cred)
+{
+       if (sess) {
+               ret = pam_close_session(pamh, 0);
+               if (ret != PAM_SUCCESS)
+                       errx(1, "pam_close_session: %s", pam_strerror(pamh, ret));
+       }
+       if (cred) {
+               ret = pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
+               if (ret != PAM_SUCCESS)
+                       warn("pam_setcred(?, PAM_DELETE_CRED | PAM_SILENT): %s",
+                           pam_strerror(pamh, ret));
+       }
+       pam_end(pamh, ret);
+}
+
+void
+watchsession(pid_t child, int sess, int cred)
+{
+       sigset_t sigs;
+       struct sigaction act, oldact;
+       int status = 1;
+
+       /* block signals */
+       sigfillset(&sigs);
+       if (sigprocmask(SIG_BLOCK, &sigs, NULL)) {
+               warn("failed to block signals");
+               caught_signal = 1;
+               goto close;
+       }
+
+       /* setup signal handler */
+       act.sa_handler = catchsig;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = 0;
+
+       /* unblock SIGTERM and SIGALRM to catch them */
+       sigemptyset(&sigs);
+       if (sigaddset(&sigs, SIGTERM) ||
+           sigaddset(&sigs, SIGALRM) ||
+           sigaddset(&sigs, SIGTSTP) ||
+           sigaction(SIGTERM, &act, &oldact) ||
+           sigprocmask(SIG_UNBLOCK, &sigs, NULL)) {
+               warn("failed to set signal handler");
+               caught_signal = 1;
+               goto close;
+       }
+
+       /* wait for child to be terminated */
+       if (waitpid(child, &status, 0) != -1) {
+               if (WIFSIGNALED(status)) {
+                       fprintf(stderr, "%s%s\n", strsignal(WTERMSIG(status)),
+                                       WCOREDUMP(status) ? " (core dumped)" : "");
+                       status = WTERMSIG(status) + 128;
+               } else
+                       status = WEXITSTATUS(status);
+       }
+       else if (caught_signal)
+               status = caught_signal + 128;
+       else
+               status = 1;
+
+close:
+       if (caught_signal && child != (pid_t)-1) {
+               fprintf(stderr, "\nSession terminated, killing shell\n");
+               kill(child, SIGTERM);
+       }
+
+       pamcleanup(PAM_SUCCESS, sess, cred);
+
+       if (caught_signal) {
+               if (child != (pid_t)-1) {
+                       /* kill child */
+                       sleep(2);
+                       kill(child, SIGKILL);
+                       fprintf(stderr, " ...killed.\n");
+               }
+
+               /* unblock cached signal and resend */
+               sigaction(SIGTERM, &oldact, NULL);
+               if (caught_signal != SIGTERM)
+                       caught_signal = SIGKILL;
+               kill(getpid(), caught_signal);
+       }
+
+       exit(status);
+}
+
+void
+pamauth(const char *user, const char *myname, int interactive, int nopass, int persist)
 {
        static const struct pam_conv conv = {
-               .conv = doas_pam_conv,
+               .conv = pamconv,
                .appdata_ptr = NULL,
        };
-       const char *ttydev, *tty;
+       const char *ttydev;
        pid_t child;
-       int ret;
+       int ret, sess = 0, cred = 0;
 
-       if (!user || !ruser)
-               return 0;
+#ifdef USE_TIMESTAMP
+       int fd = -1;
+       int valid = 0;
+#else
+       (void) persist;
+#endif
 
-       /* pam needs the real root */
-       if(setuid(0))
-               errx(1, "setuid");
+       if (!user || !myname)
+               errx(1, "Authorization failed");
 
-       ret = pam_start(PAM_SERVICE_NAME, ruser, &conv, &pamh);
+       ret = pam_start(PAM_SERVICE_NAME, myname, &conv, &pamh);
        if (ret != PAM_SUCCESS)
-               errx(1, "pam_start(\"%s\", \"%s\", ?, ?): failed\n",
-                   PAM_SERVICE_NAME, ruser);
+               errx(1, "pam_start(\"%s\", \"%s\", ?, ?): failed",
+                   PAM_SERVICE_NAME, myname);
 
-       ret = pam_set_item(pamh, PAM_RUSER, ruser);
+       ret = pam_set_item(pamh, PAM_RUSER, myname);
        if (ret != PAM_SUCCESS)
-               warn("pam_set_item(?, PAM_RUSER, \"%s\"): %s\n",
-                   pam_strerror(pamh, ret), ruser);
+               warn("pam_set_item(?, PAM_RUSER, \"%s\"): %s",
+                   pam_strerror(pamh, ret), myname);
 
        if (isatty(0) && (ttydev = ttyname(0)) != NULL) {
-               if (strncmp(ttydev, "/dev/", 5))
-                       tty = ttydev + 5;
-               else
-                       tty = ttydev;
+               if (strncmp(ttydev, "/dev/", 5) == 0)
+                       ttydev += 5;
 
-               ret = pam_set_item(pamh, PAM_TTY, tty);
+               ret = pam_set_item(pamh, PAM_TTY, ttydev);
                if (ret != PAM_SUCCESS)
-                       warn("pam_set_item(?, PAM_TTY, \"%s\"): %s\n",
-                           tty, pam_strerror(pamh, ret));
+                       warn("pam_set_item(?, PAM_TTY, \"%s\"): %s",
+                           ttydev, pam_strerror(pamh, ret));
        }
 
+
+#ifdef USE_TIMESTAMP
+       if (persist)
+               fd = timestamp_open(&valid, 5 * 60);
+       if (fd != -1 && valid == 1)
+               nopass = 1;
+#endif
+
        if (!nopass) {
                if (!interactive)
                        errx(1, "Authorization required");
@@ -172,118 +276,66 @@ doas_pam(const char *user, const char* ruser, int interactive, int nopass)
                if (gethostname(host, sizeof(host)))
                        snprintf(host, sizeof(host), "?");
                snprintf(doas_prompt, sizeof(doas_prompt),
-                   "\rdoas (%.32s@%.32s) password: ", ruser, host);
+                   "\rdoas (%.32s@%.32s) password: ", myname, host);
 
                /* authenticate */
                ret = pam_authenticate(pamh, 0);
                if (ret != PAM_SUCCESS) {
-                       pam_end(pamh, ret);
-                       return 0;
+                       pamcleanup(ret, sess, cred);
+                       syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname);
+                       errx(1, "Authorization failed");
                }
        }
 
+
        ret = pam_acct_mgmt(pamh, 0);
        if (ret == PAM_NEW_AUTHTOK_REQD)
                ret = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
 
        /* account not vaild or changing the auth token failed */
-       if (ret != PAM_SUCCESS)
-               return 0;
+       if (ret != PAM_SUCCESS) {
+               pamcleanup(ret, sess, cred);
+               syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname);
+               errx(1, "Authorization failed");
+       }
 
+       /* set PAM_USER to the user we want to be */
        ret = pam_set_item(pamh, PAM_USER, user);
        if (ret != PAM_SUCCESS)
-               warn("pam_set_item(?, PAM_USER, \"%s\"): %s\n", user,
+               warn("pam_set_item(?, PAM_USER, \"%s\"): %s", user,
                    pam_strerror(pamh, ret));
 
        ret = pam_setcred(pamh, PAM_ESTABLISH_CRED);
        if (ret != PAM_SUCCESS)
-               warn("pam_setcred(?, PAM_ESTABLISH_CRED): %s\n", pam_strerror(pamh, ret));
+               warn("pam_setcred(?, PAM_ESTABLISH_CRED): %s", pam_strerror(pamh, ret));
+       else
+               cred = 1;
 
        /* open session */
        ret = pam_open_session(pamh, 0);
        if (ret != PAM_SUCCESS)
-               errx(1, "pam_open_session(): %s\n", pam_strerror(pamh, ret));
+               errx(1, "pam_open_session: %s", pam_strerror(pamh, ret));
+       sess = 1;
 
        if ((child = fork()) == -1) {
-               pam_close_session(pamh, 0);
-               pam_end(pamh, PAM_ABORT);
-               errx(1, "fork()");
+               pamcleanup(PAM_ABORT, sess, cred);
+               err(1, "fork");
        }
 
        /* return as child */
        if (child == 0) {
-               return 1;
-       }
-
-       /* parent watches for signals and closes session */
-       sigset_t sigs;
-       struct sigaction act, oldact;
-       int status;
-
-       /* block signals */
-       sigfillset(&sigs);
-       if (sigprocmask(SIG_BLOCK, &sigs, NULL)) {
-               warn("failed to block signals");
-               caught_signal = 1;
-       }
-
-       /* setup signal handler */
-       act.sa_handler = catchsig;
-       sigemptyset(&act.sa_mask);
-       act.sa_flags = 0;
-
-       /* unblock SIGTERM and SIGALRM to catch them */
-       sigemptyset(&sigs);
-       if (sigaddset(&sigs, SIGTERM) ||
-           sigaddset(&sigs, SIGALRM) ||
-           sigaction(SIGTERM, &act, &oldact) ||
-           sigprocmask(SIG_UNBLOCK, &sigs, NULL)) {
-               warn("failed to set signal handler");
-               caught_signal = 1;
-       }
-
-       if (!caught_signal) {
-               /* wait for child to be terminated */
-               if (waitpid(child, &status, 0) != -1) {
-                       if (WIFSIGNALED(status)) {
-                               fprintf(stderr, "%s%s\n", strsignal(WTERMSIG(status)),
-                                   WCOREDUMP(status) ? " (core dumped)" : "");
-                               status = WTERMSIG(status) + 128;
-                       } else {
-                               status = WEXITSTATUS(status);
-                       }
-               }
-               else if (caught_signal)
-                       status = caught_signal + 128;
-               else
-                       status = 1;
-       }
-
-       if (caught_signal) {
-               fprintf(stderr, "\nSession terminated, killing shell\n");
-               kill(child, SIGTERM);
+#ifdef USE_TIMESTAMP
+               if (fd != -1)
+                       close(fd);
+#endif
+               return;
        }
 
-       /* close session */
-       ret = pam_close_session(pamh, 0);
-       if (ret != PAM_SUCCESS)
-               errx(1, "pam_close_session(): %s\n", pam_strerror(pamh, ret));
-
-       pam_end(pamh, PAM_SUCCESS);
-
-       if (caught_signal) {
-               /* kill child */
-               sleep(2);
-               kill(child, SIGKILL);
-               fprintf(stderr, " ...killed.\n");
-
-               /* unblock cached signal and resend */
-               sigaction(SIGTERM, &oldact, NULL);
-               if (caught_signal != SIGTERM)
-                       caught_signal = SIGKILL;
-               kill(getpid(), caught_signal);
+#ifdef USE_TIMESTAMP
+       if (fd != -1) {
+               timestamp_set(fd, 5 * 60);
+               close(fd);
        }
-
-       exit(status);
-       return 0;
+#endif
+       watchsession(child, sess, cred);
 }