diff options
Diffstat (limited to 'auth-pam.c')
-rw-r--r-- | auth-pam.c | 206 |
1 files changed, 187 insertions, 19 deletions
diff --git a/auth-pam.c b/auth-pam.c index 36dbb7e15..b93241f48 100644 --- a/auth-pam.c +++ b/auth-pam.c | |||
@@ -28,10 +28,26 @@ | |||
28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
29 | * SUCH DAMAGE. | 29 | * SUCH DAMAGE. |
30 | */ | 30 | */ |
31 | /* | ||
32 | * Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org> | ||
33 | * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au> | ||
34 | * | ||
35 | * Permission to use, copy, modify, and distribute this software for any | ||
36 | * purpose with or without fee is hereby granted, provided that the above | ||
37 | * copyright notice and this permission notice appear in all copies. | ||
38 | * | ||
39 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
40 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
41 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
42 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
43 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
44 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
45 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
46 | */ | ||
31 | 47 | ||
32 | /* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */ | 48 | /* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */ |
33 | #include "includes.h" | 49 | #include "includes.h" |
34 | RCSID("$Id: auth-pam.c,v 1.100 2004/04/18 01:00:26 dtucker Exp $"); | 50 | RCSID("$Id: auth-pam.c,v 1.114 2004/08/16 13:12:06 dtucker Exp $"); |
35 | 51 | ||
36 | #ifdef USE_PAM | 52 | #ifdef USE_PAM |
37 | #if defined(HAVE_SECURITY_PAM_APPL_H) | 53 | #if defined(HAVE_SECURITY_PAM_APPL_H) |
@@ -49,7 +65,7 @@ RCSID("$Id: auth-pam.c,v 1.100 2004/04/18 01:00:26 dtucker Exp $"); | |||
49 | #include "monitor_wrap.h" | 65 | #include "monitor_wrap.h" |
50 | #include "msg.h" | 66 | #include "msg.h" |
51 | #include "packet.h" | 67 | #include "packet.h" |
52 | #include "readpass.h" | 68 | #include "misc.h" |
53 | #include "servconf.h" | 69 | #include "servconf.h" |
54 | #include "ssh2.h" | 70 | #include "ssh2.h" |
55 | #include "xmalloc.h" | 71 | #include "xmalloc.h" |
@@ -93,10 +109,17 @@ static mysig_t sshpam_oldsig; | |||
93 | static void | 109 | static void |
94 | sshpam_sigchld_handler(int sig) | 110 | sshpam_sigchld_handler(int sig) |
95 | { | 111 | { |
112 | signal(SIGCHLD, SIG_DFL); | ||
96 | if (cleanup_ctxt == NULL) | 113 | if (cleanup_ctxt == NULL) |
97 | return; /* handler called after PAM cleanup, shouldn't happen */ | 114 | return; /* handler called after PAM cleanup, shouldn't happen */ |
98 | if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0) == -1) | 115 | if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG) |
99 | return; /* couldn't wait for process */ | 116 | <= 0) { |
117 | /* PAM thread has not exitted, privsep slave must have */ | ||
118 | kill(cleanup_ctxt->pam_thread, SIGTERM); | ||
119 | if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0) | ||
120 | <= 0) | ||
121 | return; /* could not wait */ | ||
122 | } | ||
100 | if (WIFSIGNALED(sshpam_thread_status) && | 123 | if (WIFSIGNALED(sshpam_thread_status) && |
101 | WTERMSIG(sshpam_thread_status) == SIGTERM) | 124 | WTERMSIG(sshpam_thread_status) == SIGTERM) |
102 | return; /* terminated by pthread_cancel */ | 125 | return; /* terminated by pthread_cancel */ |
@@ -162,6 +185,7 @@ static int sshpam_cred_established = 0; | |||
162 | static int sshpam_account_status = -1; | 185 | static int sshpam_account_status = -1; |
163 | static char **sshpam_env = NULL; | 186 | static char **sshpam_env = NULL; |
164 | static Authctxt *sshpam_authctxt = NULL; | 187 | static Authctxt *sshpam_authctxt = NULL; |
188 | static const char *sshpam_password = NULL; | ||
165 | 189 | ||
166 | /* Some PAM implementations don't implement this */ | 190 | /* Some PAM implementations don't implement this */ |
167 | #ifndef HAVE_PAM_GETENVLIST | 191 | #ifndef HAVE_PAM_GETENVLIST |
@@ -177,8 +201,33 @@ pam_getenvlist(pam_handle_t *pamh) | |||
177 | } | 201 | } |
178 | #endif | 202 | #endif |
179 | 203 | ||
204 | /* | ||
205 | * Some platforms, notably Solaris, do not enforce password complexity | ||
206 | * rules during pam_chauthtok() if the real uid of the calling process | ||
207 | * is 0, on the assumption that it's being called by "passwd" run by root. | ||
208 | * This wraps pam_chauthtok and sets/restore the real uid so PAM will do | ||
209 | * the right thing. | ||
210 | */ | ||
211 | #ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID | ||
212 | static int | ||
213 | sshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) | ||
214 | { | ||
215 | int result; | ||
216 | |||
217 | if (sshpam_authctxt == NULL) | ||
218 | fatal("PAM: sshpam_authctxt not initialized"); | ||
219 | if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1) | ||
220 | fatal("%s: setreuid failed: %s", __func__, strerror(errno)); | ||
221 | result = pam_chauthtok(pamh, flags); | ||
222 | if (setreuid(0, -1) == -1) | ||
223 | fatal("%s: setreuid failed: %s", __func__, strerror(errno)); | ||
224 | return result; | ||
225 | } | ||
226 | # define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) | ||
227 | #endif | ||
228 | |||
180 | void | 229 | void |
181 | pam_password_change_required(int reqd) | 230 | sshpam_password_change_required(int reqd) |
182 | { | 231 | { |
183 | debug3("%s %d", __func__, reqd); | 232 | debug3("%s %d", __func__, reqd); |
184 | if (sshpam_authctxt == NULL) | 233 | if (sshpam_authctxt == NULL) |
@@ -208,7 +257,7 @@ import_environments(Buffer *b) | |||
208 | #ifndef USE_POSIX_THREADS | 257 | #ifndef USE_POSIX_THREADS |
209 | /* Import variables set by do_pam_account */ | 258 | /* Import variables set by do_pam_account */ |
210 | sshpam_account_status = buffer_get_int(b); | 259 | sshpam_account_status = buffer_get_int(b); |
211 | pam_password_change_required(buffer_get_int(b)); | 260 | sshpam_password_change_required(buffer_get_int(b)); |
212 | 261 | ||
213 | /* Import environment from subprocess */ | 262 | /* Import environment from subprocess */ |
214 | num_env = buffer_get_int(b); | 263 | num_env = buffer_get_int(b); |
@@ -240,7 +289,7 @@ import_environments(Buffer *b) | |||
240 | * Conversation function for authentication thread. | 289 | * Conversation function for authentication thread. |
241 | */ | 290 | */ |
242 | static int | 291 | static int |
243 | sshpam_thread_conv(int n, const struct pam_message **msg, | 292 | sshpam_thread_conv(int n, struct pam_message **msg, |
244 | struct pam_response **resp, void *data) | 293 | struct pam_response **resp, void *data) |
245 | { | 294 | { |
246 | Buffer buffer; | 295 | Buffer buffer; |
@@ -251,6 +300,10 @@ sshpam_thread_conv(int n, const struct pam_message **msg, | |||
251 | debug3("PAM: %s entering, %d messages", __func__, n); | 300 | debug3("PAM: %s entering, %d messages", __func__, n); |
252 | *resp = NULL; | 301 | *resp = NULL; |
253 | 302 | ||
303 | if (data == NULL) { | ||
304 | error("PAM: conversation function passed a null context"); | ||
305 | return (PAM_CONV_ERR); | ||
306 | } | ||
254 | ctxt = data; | 307 | ctxt = data; |
255 | if (n <= 0 || n > PAM_MAX_NUM_MSG) | 308 | if (n <= 0 || n > PAM_MAX_NUM_MSG) |
256 | return (PAM_CONV_ERR); | 309 | return (PAM_CONV_ERR); |
@@ -328,15 +381,21 @@ sshpam_thread(void *ctxtp) | |||
328 | struct pam_ctxt *ctxt = ctxtp; | 381 | struct pam_ctxt *ctxt = ctxtp; |
329 | Buffer buffer; | 382 | Buffer buffer; |
330 | struct pam_conv sshpam_conv; | 383 | struct pam_conv sshpam_conv; |
384 | int flags = (options.permit_empty_passwd == 0 ? | ||
385 | PAM_DISALLOW_NULL_AUTHTOK : 0); | ||
331 | #ifndef USE_POSIX_THREADS | 386 | #ifndef USE_POSIX_THREADS |
332 | extern char **environ; | 387 | extern char **environ; |
333 | char **env_from_pam; | 388 | char **env_from_pam; |
334 | u_int i; | 389 | u_int i; |
335 | const char *pam_user; | 390 | const char *pam_user; |
336 | 391 | ||
337 | pam_get_item(sshpam_handle, PAM_USER, (const void **)&pam_user); | 392 | pam_get_item(sshpam_handle, PAM_USER, (void **)&pam_user); |
338 | setproctitle("%s [pam]", pam_user); | ||
339 | environ[0] = NULL; | 393 | environ[0] = NULL; |
394 | |||
395 | if (sshpam_authctxt != NULL) { | ||
396 | setproctitle("%s [pam]", | ||
397 | sshpam_authctxt->valid ? pam_user : "unknown"); | ||
398 | } | ||
340 | #endif | 399 | #endif |
341 | 400 | ||
342 | sshpam_conv.conv = sshpam_thread_conv; | 401 | sshpam_conv.conv = sshpam_thread_conv; |
@@ -350,7 +409,7 @@ sshpam_thread(void *ctxtp) | |||
350 | (const void *)&sshpam_conv); | 409 | (const void *)&sshpam_conv); |
351 | if (sshpam_err != PAM_SUCCESS) | 410 | if (sshpam_err != PAM_SUCCESS) |
352 | goto auth_fail; | 411 | goto auth_fail; |
353 | sshpam_err = pam_authenticate(sshpam_handle, 0); | 412 | sshpam_err = pam_authenticate(sshpam_handle, flags); |
354 | if (sshpam_err != PAM_SUCCESS) | 413 | if (sshpam_err != PAM_SUCCESS) |
355 | goto auth_fail; | 414 | goto auth_fail; |
356 | 415 | ||
@@ -362,7 +421,7 @@ sshpam_thread(void *ctxtp) | |||
362 | PAM_CHANGE_EXPIRED_AUTHTOK); | 421 | PAM_CHANGE_EXPIRED_AUTHTOK); |
363 | if (sshpam_err != PAM_SUCCESS) | 422 | if (sshpam_err != PAM_SUCCESS) |
364 | goto auth_fail; | 423 | goto auth_fail; |
365 | pam_password_change_required(0); | 424 | sshpam_password_change_required(0); |
366 | } | 425 | } |
367 | } | 426 | } |
368 | 427 | ||
@@ -422,7 +481,7 @@ sshpam_thread_cleanup(void) | |||
422 | } | 481 | } |
423 | 482 | ||
424 | static int | 483 | static int |
425 | sshpam_null_conv(int n, const struct pam_message **msg, | 484 | sshpam_null_conv(int n, struct pam_message **msg, |
426 | struct pam_response **resp, void *data) | 485 | struct pam_response **resp, void *data) |
427 | { | 486 | { |
428 | debug3("PAM: %s entering, %d messages", __func__, n); | 487 | debug3("PAM: %s entering, %d messages", __func__, n); |
@@ -460,7 +519,7 @@ sshpam_init(Authctxt *authctxt) | |||
460 | if (sshpam_handle != NULL) { | 519 | if (sshpam_handle != NULL) { |
461 | /* We already have a PAM context; check if the user matches */ | 520 | /* We already have a PAM context; check if the user matches */ |
462 | sshpam_err = pam_get_item(sshpam_handle, | 521 | sshpam_err = pam_get_item(sshpam_handle, |
463 | PAM_USER, (const void **)&pam_user); | 522 | PAM_USER, (void **)&pam_user); |
464 | if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) | 523 | if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) |
465 | return (0); | 524 | return (0); |
466 | pam_end(sshpam_handle, sshpam_err); | 525 | pam_end(sshpam_handle, sshpam_err); |
@@ -712,7 +771,7 @@ do_pam_account(void) | |||
712 | } | 771 | } |
713 | 772 | ||
714 | if (sshpam_err == PAM_NEW_AUTHTOK_REQD) | 773 | if (sshpam_err == PAM_NEW_AUTHTOK_REQD) |
715 | pam_password_change_required(1); | 774 | sshpam_password_change_required(1); |
716 | 775 | ||
717 | sshpam_account_status = 1; | 776 | sshpam_account_status = 1; |
718 | return (sshpam_account_status); | 777 | return (sshpam_account_status); |
@@ -758,7 +817,7 @@ do_pam_setcred(int init) | |||
758 | } | 817 | } |
759 | 818 | ||
760 | static int | 819 | static int |
761 | pam_tty_conv(int n, const struct pam_message **msg, | 820 | sshpam_tty_conv(int n, struct pam_message **msg, |
762 | struct pam_response **resp, void *data) | 821 | struct pam_response **resp, void *data) |
763 | { | 822 | { |
764 | char input[PAM_MAX_MSG_SIZE]; | 823 | char input[PAM_MAX_MSG_SIZE]; |
@@ -787,7 +846,8 @@ pam_tty_conv(int n, const struct pam_message **msg, | |||
787 | case PAM_PROMPT_ECHO_ON: | 846 | case PAM_PROMPT_ECHO_ON: |
788 | fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); | 847 | fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); |
789 | fgets(input, sizeof input, stdin); | 848 | fgets(input, sizeof input, stdin); |
790 | reply[i].resp = xstrdup(input); | 849 | if ((reply[i].resp = strdup(input)) == NULL) |
850 | goto fail; | ||
791 | reply[i].resp_retcode = PAM_SUCCESS; | 851 | reply[i].resp_retcode = PAM_SUCCESS; |
792 | break; | 852 | break; |
793 | case PAM_ERROR_MSG: | 853 | case PAM_ERROR_MSG: |
@@ -811,7 +871,7 @@ pam_tty_conv(int n, const struct pam_message **msg, | |||
811 | return (PAM_CONV_ERR); | 871 | return (PAM_CONV_ERR); |
812 | } | 872 | } |
813 | 873 | ||
814 | static struct pam_conv tty_conv = { pam_tty_conv, NULL }; | 874 | static struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; |
815 | 875 | ||
816 | /* | 876 | /* |
817 | * XXX this should be done in the authentication phase, but ssh1 doesn't | 877 | * XXX this should be done in the authentication phase, but ssh1 doesn't |
@@ -835,7 +895,7 @@ do_pam_chauthtok(void) | |||
835 | } | 895 | } |
836 | 896 | ||
837 | static int | 897 | static int |
838 | pam_store_conv(int n, const struct pam_message **msg, | 898 | sshpam_store_conv(int n, struct pam_message **msg, |
839 | struct pam_response **resp, void *data) | 899 | struct pam_response **resp, void *data) |
840 | { | 900 | { |
841 | struct pam_response *reply; | 901 | struct pam_response *reply; |
@@ -877,7 +937,7 @@ pam_store_conv(int n, const struct pam_message **msg, | |||
877 | return (PAM_CONV_ERR); | 937 | return (PAM_CONV_ERR); |
878 | } | 938 | } |
879 | 939 | ||
880 | static struct pam_conv store_conv = { pam_store_conv, NULL }; | 940 | static struct pam_conv store_conv = { sshpam_store_conv, NULL }; |
881 | 941 | ||
882 | void | 942 | void |
883 | do_pam_session(void) | 943 | do_pam_session(void) |
@@ -944,4 +1004,112 @@ free_pam_environment(char **env) | |||
944 | xfree(env); | 1004 | xfree(env); |
945 | } | 1005 | } |
946 | 1006 | ||
1007 | /* | ||
1008 | * "Blind" conversation function for password authentication. Assumes that | ||
1009 | * echo-off prompts are for the password and stores messages for later | ||
1010 | * display. | ||
1011 | */ | ||
1012 | static int | ||
1013 | sshpam_passwd_conv(int n, struct pam_message **msg, | ||
1014 | struct pam_response **resp, void *data) | ||
1015 | { | ||
1016 | struct pam_response *reply; | ||
1017 | int i; | ||
1018 | size_t len; | ||
1019 | |||
1020 | debug3("PAM: %s called with %d messages", __func__, n); | ||
1021 | |||
1022 | *resp = NULL; | ||
1023 | |||
1024 | if (n <= 0 || n > PAM_MAX_NUM_MSG) | ||
1025 | return (PAM_CONV_ERR); | ||
1026 | |||
1027 | if ((reply = malloc(n * sizeof(*reply))) == NULL) | ||
1028 | return (PAM_CONV_ERR); | ||
1029 | memset(reply, 0, n * sizeof(*reply)); | ||
1030 | |||
1031 | for (i = 0; i < n; ++i) { | ||
1032 | switch (PAM_MSG_MEMBER(msg, i, msg_style)) { | ||
1033 | case PAM_PROMPT_ECHO_OFF: | ||
1034 | if (sshpam_password == NULL) | ||
1035 | goto fail; | ||
1036 | if ((reply[i].resp = strdup(sshpam_password)) == NULL) | ||
1037 | goto fail; | ||
1038 | reply[i].resp_retcode = PAM_SUCCESS; | ||
1039 | break; | ||
1040 | case PAM_ERROR_MSG: | ||
1041 | case PAM_TEXT_INFO: | ||
1042 | len = strlen(PAM_MSG_MEMBER(msg, i, msg)); | ||
1043 | if (len > 0) { | ||
1044 | buffer_append(&loginmsg, | ||
1045 | PAM_MSG_MEMBER(msg, i, msg), len); | ||
1046 | buffer_append(&loginmsg, "\n", 1); | ||
1047 | } | ||
1048 | if ((reply[i].resp = strdup("")) == NULL) | ||
1049 | goto fail; | ||
1050 | reply[i].resp_retcode = PAM_SUCCESS; | ||
1051 | break; | ||
1052 | default: | ||
1053 | goto fail; | ||
1054 | } | ||
1055 | } | ||
1056 | *resp = reply; | ||
1057 | return (PAM_SUCCESS); | ||
1058 | |||
1059 | fail: | ||
1060 | for(i = 0; i < n; i++) { | ||
1061 | if (reply[i].resp != NULL) | ||
1062 | xfree(reply[i].resp); | ||
1063 | } | ||
1064 | xfree(reply); | ||
1065 | return (PAM_CONV_ERR); | ||
1066 | } | ||
1067 | |||
1068 | static struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; | ||
1069 | |||
1070 | /* | ||
1071 | * Attempt password authentication via PAM | ||
1072 | */ | ||
1073 | int | ||
1074 | sshpam_auth_passwd(Authctxt *authctxt, const char *password) | ||
1075 | { | ||
1076 | int flags = (options.permit_empty_passwd == 0 ? | ||
1077 | PAM_DISALLOW_NULL_AUTHTOK : 0); | ||
1078 | static char badpw[] = "\b\n\r\177INCORRECT"; | ||
1079 | |||
1080 | if (!options.use_pam || sshpam_handle == NULL) | ||
1081 | fatal("PAM: %s called when PAM disabled or failed to " | ||
1082 | "initialise.", __func__); | ||
1083 | |||
1084 | sshpam_password = password; | ||
1085 | sshpam_authctxt = authctxt; | ||
1086 | |||
1087 | /* | ||
1088 | * If the user logging in is invalid, or is root but is not permitted | ||
1089 | * by PermitRootLogin, use an invalid password to prevent leaking | ||
1090 | * information via timing (eg if the PAM config has a delay on fail). | ||
1091 | */ | ||
1092 | if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && | ||
1093 | options.permit_root_login != PERMIT_YES)) | ||
1094 | sshpam_password = badpw; | ||
1095 | |||
1096 | sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, | ||
1097 | (const void *)&passwd_conv); | ||
1098 | if (sshpam_err != PAM_SUCCESS) | ||
1099 | fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, | ||
1100 | pam_strerror(sshpam_handle, sshpam_err)); | ||
1101 | |||
1102 | sshpam_err = pam_authenticate(sshpam_handle, flags); | ||
1103 | sshpam_password = NULL; | ||
1104 | if (sshpam_err == PAM_SUCCESS && authctxt->valid) { | ||
1105 | debug("PAM: password authentication accepted for %.100s", | ||
1106 | authctxt->user); | ||
1107 | return 1; | ||
1108 | } else { | ||
1109 | debug("PAM: password authentication failed for %.100s: %s", | ||
1110 | authctxt->valid ? authctxt->user : "an illegal user", | ||
1111 | pam_strerror(sshpam_handle, sshpam_err)); | ||
1112 | return 0; | ||
1113 | } | ||
1114 | } | ||
947 | #endif /* USE_PAM */ | 1115 | #endif /* USE_PAM */ |