Imported Upstream version 1.7.6p1
[debian/sudo] / auth / pam.c
index b2fe41a745671b84cf46f6500569c52c0d263904..265de36e3ca47838f0e8270fea716eb6ae2cb01d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2005 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 1999-2005, 2007-2010 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
 # endif
 #endif /* STDC_HEADERS */
 #ifdef HAVE_STRING_H
-# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
-#  include <memory.h>
-# endif
 # include <string.h>
-#else
-# ifdef HAVE_STRINGS_H
-#  include <strings.h>
-# endif
 #endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif /* HAVE_UNISTD_H */
 #include <pwd.h>
+#include <errno.h>
 
 #ifdef HAVE_PAM_PAM_APPL_H
 # include <pam/pam_appl.h>
 #include "sudo_auth.h"
 
 /* Only OpenPAM and Linux PAM use const qualifiers. */
-#if defined(_OPENPAM) || defined(__LIBPAM_VERSION) || defined(__LINUX_PAM__)
+#if defined(_OPENPAM) || defined(OPENPAM_VERSION) || \
+    defined(__LIBPAM_VERSION) || defined(__LINUX_PAM__)
 # define PAM_CONST     const
 #else
 # define PAM_CONST
 #endif
 
-#ifndef lint
-__unused static const char rcsid[] = "$Sudo: pam.c,v 1.43.2.10 2008/02/22 20:19:45 millert Exp $";
-#endif /* lint */
-
 static int sudo_conv __P((int, PAM_CONST struct pam_message **,
-                         struct pam_response **, VOID *));
-static char *def_prompt;
+                         struct pam_response **, void *));
+static char *def_prompt = "Password:";
+static int gotintr;
 
 #ifndef PAM_DATA_SILENT
 #define PAM_DATA_SILENT        0
@@ -96,13 +91,29 @@ pam_init(pw, promptp, auth)
 
     /* Initial PAM setup */
     if (auth != NULL)
-       auth->data = (VOID *) &pam_status;
+       auth->data = (void *) &pam_status;
     pam_conv.conv = sudo_conv;
-    pam_status = pam_start("sudo", pw->pw_name, &pam_conv, &pamh);
+#ifdef HAVE_PAM_LOGIN
+    if (ISSET(sudo_mode, MODE_LOGIN_SHELL))
+       pam_status = pam_start("sudo-i", pw->pw_name, &pam_conv, &pamh);
+    else
+#endif
+       pam_status = pam_start("sudo", pw->pw_name, &pam_conv, &pamh);
+
     if (pam_status != PAM_SUCCESS) {
        log_error(USE_ERRNO|NO_EXIT|NO_MAIL, "unable to initialize PAM");
-       return(AUTH_FATAL);
+       return AUTH_FATAL;
     }
+
+    /*
+     * Set PAM_RUSER to the invoking user (the "from" user).
+     * We set PAM_RHOST to avoid a bug in Solaris 7 and below.
+     */
+    (void) pam_set_item(pamh, PAM_RUSER, user_name);
+#ifdef __sun__
+    (void) pam_set_item(pamh, PAM_RHOST, user_host);
+#endif
+
     /*
      * Some versions of pam_lastlog have a bug that
      * will cause a crash if PAM_TTY is not set so if
@@ -113,7 +124,7 @@ pam_init(pw, promptp, auth)
     else
        (void) pam_set_item(pamh, PAM_TTY, user_ttypath);
 
-    return(AUTH_SUCCESS);
+    return AUTH_SUCCESS;
 }
 
 int
@@ -134,11 +145,11 @@ pam_verify(pw, prompt, auth)
            *pam_status = pam_acct_mgmt(pamh, PAM_SILENT);
            switch (*pam_status) {
                case PAM_SUCCESS:
-                   return(AUTH_SUCCESS);
+                   return AUTH_SUCCESS;
                case PAM_AUTH_ERR:
-                   log_error(NO_EXIT|NO_MAIL, "pam_acct_mgmt: %d",
-                       *pam_status);
-                   return(AUTH_FAILURE);
+                   log_error(NO_EXIT|NO_MAIL,
+                       "account validation failure, is your account locked?");
+                   return AUTH_FATAL;
                case PAM_NEW_AUTHTOK_REQD:
                    log_error(NO_EXIT|NO_MAIL, "%s, %s",
                        "Account or password is expired",
@@ -146,29 +157,33 @@ pam_verify(pw, prompt, auth)
                    *pam_status = pam_chauthtok(pamh,
                        PAM_CHANGE_EXPIRED_AUTHTOK);
                    if (*pam_status == PAM_SUCCESS)
-                       return(AUTH_SUCCESS);
+                       return AUTH_SUCCESS;
                    if ((s = pam_strerror(pamh, *pam_status)))
                        log_error(NO_EXIT|NO_MAIL, "pam_chauthtok: %s", s);
-                   return(AUTH_FAILURE);
+                   return AUTH_FAILURE;
                case PAM_AUTHTOK_EXPIRED:
                    log_error(NO_EXIT|NO_MAIL,
                        "Password expired, contact your system administrator");
-                   return(AUTH_FATAL);
+                   return AUTH_FATAL;
                case PAM_ACCT_EXPIRED:
                    log_error(NO_EXIT|NO_MAIL, "%s %s",
                        "Account expired or PAM config lacks an \"account\"",
                        "section for sudo, contact your system administrator");
-                   return(AUTH_FATAL);
+                   return AUTH_FATAL;
            }
            /* FALLTHROUGH */
        case PAM_AUTH_ERR:
+           if (gotintr) {
+               /* error or ^C from tgetpass() */
+               return AUTH_INTR;
+           }
        case PAM_MAXTRIES:
        case PAM_PERM_DENIED:
-           return(AUTH_FAILURE);
+           return AUTH_FAILURE;
        default:
            if ((s = pam_strerror(pamh, *pam_status)))
                log_error(NO_EXIT|NO_MAIL, "pam_authenticate: %s", s);
-           return(AUTH_FATAL);
+           return AUTH_FATAL;
     }
 }
 
@@ -181,29 +196,27 @@ pam_cleanup(pw, auth)
 
     /* If successful, we can't close the session until pam_prep_user() */
     if (auth->status == AUTH_SUCCESS)
-       return(AUTH_SUCCESS);
+       return AUTH_SUCCESS;
 
     *pam_status = pam_end(pamh, *pam_status | PAM_DATA_SILENT);
-    return(*pam_status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE);
+    return *pam_status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE;
 }
 
 int
-pam_prep_user(pw)
+pam_begin_session(pw)
     struct passwd *pw;
 {
-    int eval;
+    int status =  PAM_SUCCESS;
 
+    /* If the user did not have to authenticate there is no pam handle yet. */
     if (pamh == NULL)
        pam_init(pw, NULL, NULL);
 
     /*
-     * Set PAM_USER to the user we are changing *to* and
-     * set PAM_RUSER to the user we are coming *from*.
-     * We set PAM_RHOST to avoid a bug in Solaris 7 and below.
+     * Update PAM_USER to reference the user we are running the command
+     * as, as opposed to the user we authenticated as.
      */
     (void) pam_set_item(pamh, PAM_USER, pw->pw_name);
-    (void) pam_set_item(pamh, PAM_RUSER, user_name);
-    (void) pam_set_item(pamh, PAM_RHOST, user_host);
 
     /*
      * Set credentials (may include resource limits, device ownership, etc).
@@ -216,23 +229,27 @@ pam_prep_user(pw)
     (void) pam_setcred(pamh, PAM_ESTABLISH_CRED);
 
 #ifndef NO_PAM_SESSION
-    /*
-     * To fully utilize PAM sessions we would need to keep a
-     * sudo process around until the command exits.  However, we
-     * can at least cause pam_limits to be run by opening and then
-     * immediately closing the session.
-     */
-    if ((eval = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
-       (void) pam_end(pamh, eval | PAM_DATA_SILENT);
-       return(AUTH_FAILURE);
+    status = pam_open_session(pamh, 0);
+     if (status != PAM_SUCCESS) {
+       (void) pam_end(pamh, status | PAM_DATA_SILENT);
+       pamh = NULL;
     }
-    (void) pam_close_session(pamh, 0);
 #endif
+    return status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE;
+}
 
-    if (pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT) == PAM_SUCCESS)
-       return(AUTH_SUCCESS);
-    else
-       return(AUTH_FAILURE);
+int
+pam_end_session()
+{
+    int status = PAM_SUCCESS;
+
+    if (pamh != NULL) {
+#ifndef NO_PAM_SESSION
+       (void) pam_close_session(pamh, 0);
+#endif
+       status = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
+    }
+    return status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE;
 }
 
 /*
@@ -244,17 +261,16 @@ sudo_conv(num_msg, msg, response, appdata_ptr)
     int num_msg;
     PAM_CONST struct pam_message **msg;
     struct pam_response **response;
-    VOID *appdata_ptr;
+    void *appdata_ptr;
 {
     struct pam_response *pr;
     PAM_CONST struct pam_message *pm;
     const char *prompt;
     char *pass;
     int n, flags, std_prompt;
-    extern int nil_pw;
 
     if ((*response = malloc(num_msg * sizeof(struct pam_response))) == NULL)
-       return(PAM_CONV_ERR);
+       return PAM_SYSTEM_ERR;
     zero_bytes(*response, num_msg * sizeof(struct pam_response));
 
     for (pr = *response, pm = *msg, n = num_msg; n--; pr++, pm++) {
@@ -265,8 +281,12 @@ sudo_conv(num_msg, msg, response, appdata_ptr)
            case PAM_PROMPT_ECHO_OFF:
                prompt = def_prompt;
 
+               /* Error out if the last password read was interrupted. */
+               if (gotintr)
+                   goto err;
+
                /* Is the sudo prompt standard? (If so, we'l just use PAM's) */
-               std_prompt = strncmp(def_prompt, "Password:", 9) == 0 &&
+               std_prompt =  strncmp(def_prompt, "Password:", 9) == 0 &&
                    (def_prompt[9] == '\0' ||
                    (def_prompt[9] == ' ' && def_prompt[10] == '\0'));
 
@@ -282,18 +302,20 @@ sudo_conv(num_msg, msg, response, appdata_ptr)
                    && (pm->msg[9] != ' ' || pm->msg[10] != '\0'))))
                    prompt = pm->msg;
 #endif
-               /* Read the password. */
+               /* Read the password unless interrupted. */
                pass = tgetpass(prompt, def_passwd_timeout * 60, flags);
                if (pass == NULL) {
                    /* We got ^C instead of a password; abort quickly. */
-                   nil_pw = 1;
+                   if (errno == EINTR)
+                       gotintr = 1;
+#if defined(__darwin__) || defined(__APPLE__)
+                   pass = "";
+#else
                    goto err;
+#endif
                }
                pr->resp = estrdup(pass);
-               if (*pr->resp == '\0')
-                   nil_pw = 1;         /* empty password */
-               else
-                   zero_bytes(pass, strlen(pass));
+               zero_bytes(pass, strlen(pass));
                break;
            case PAM_TEXT_INFO:
                if (pm->msg)
@@ -310,7 +332,7 @@ sudo_conv(num_msg, msg, response, appdata_ptr)
        }
     }
 
-    return(PAM_SUCCESS);
+    return PAM_SUCCESS;
 
 err:
     /* Zero and free allocated memory and return an error. */
@@ -324,5 +346,5 @@ err:
     zero_bytes(*response, num_msg * sizeof(struct pam_response));
     free(*response);
     *response = NULL;
-    return(PAM_CONV_ERR);
+    return gotintr ? PAM_AUTH_ERR : PAM_CONV_ERR;
 }