3 Copyright Andrew Tridgell <tridge@samba.org> 2000
4 Copyright Tim Potter <tpot@samba.org> 2000
5 Copyright Andrew Bartlett <abartlet@samba.org> 2002
7 largely based on pam_userdb by Christian Gafton <gafton@redhat.com>
8 also contains large slabs of code from pam_unix by Elliot Lee <sopwith@redhat.com>
9 (see copyright below for full details)
12 #include "pam_winbind.h"
16 #define MAX_PASSWD_TRIES 3
19 static void _pam_log(int err, const char *format, ...)
23 va_start(args, format);
24 openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH);
25 vsyslog(err, format, args);
30 static int _pam_parse(int argc, const char **argv)
33 /* step through arguments */
34 for (ctrl = 0; argc-- > 0; ++argv) {
38 if (!strcmp(*argv,"debug"))
39 ctrl |= WINBIND_DEBUG_ARG;
40 else if (!strcasecmp(*argv, "use_authtok"))
41 ctrl |= WINBIND_USE_AUTHTOK_ARG;
42 else if (!strcasecmp(*argv, "use_first_pass"))
43 ctrl |= WINBIND_USE_FIRST_PASS_ARG;
44 else if (!strcasecmp(*argv, "try_first_pass"))
45 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
46 else if (!strcasecmp(*argv, "unknown_ok"))
47 ctrl |= WINBIND_UNKNOWN_OK_ARG;
48 else if (!strncasecmp(*argv, "required_membership", strlen("required_membership")))
49 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
51 _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv);
58 /* --- authentication management functions --- */
60 /* Attempt a conversation */
62 static int converse(pam_handle_t *pamh, int nargs,
63 struct pam_message **message,
64 struct pam_response **response)
67 struct pam_conv *conv;
69 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ;
70 if (retval == PAM_SUCCESS) {
71 retval = conv->conv(nargs, (const struct pam_message **)message,
72 response, conv->appdata_ptr);
75 return retval; /* propagate error status */
79 static int _make_remark(pam_handle_t * pamh, int type, const char *text)
81 int retval = PAM_SUCCESS;
83 struct pam_message *pmsg[1], msg[1];
84 struct pam_response *resp;
88 msg[0].msg_style = type;
91 retval = converse(pamh, 1, pmsg, &resp);
94 _pam_drop_reply(resp, 1);
99 static int pam_winbind_request(enum winbindd_cmd req_type,
100 struct winbindd_request *request,
101 struct winbindd_response *response)
104 /* Fill in request and send down pipe */
105 init_request(request, req_type);
107 if (write_sock(request, sizeof(*request)) == -1) {
108 _pam_log(LOG_ERR, "write to socket failed!");
110 return PAM_SERVICE_ERR;
114 if (read_reply(response) == -1) {
115 _pam_log(LOG_ERR, "read from socket failed!");
117 return PAM_SERVICE_ERR;
120 /* We are done with the socket - close it and avoid mischeif */
123 /* Copy reply data from socket */
124 if (response->result != WINBINDD_OK) {
125 if (response->data.auth.pam_error != PAM_SUCCESS) {
126 _pam_log(LOG_ERR, "request failed: %s, PAM error was %d, NT error was %s",
127 response->data.auth.error_string,
128 response->data.auth.pam_error,
129 response->data.auth.nt_status_string);
130 return response->data.auth.pam_error;
132 _pam_log(LOG_ERR, "request failed, but PAM error 0!");
133 return PAM_SERVICE_ERR;
140 static int pam_winbind_request_log(enum winbindd_cmd req_type,
141 struct winbindd_request *request,
142 struct winbindd_response *response,
148 retval = pam_winbind_request(req_type, request, response);
152 /* incorrect password */
153 _pam_log(LOG_WARNING, "user `%s' denied access (incorrect password or invalid membership)", user);
155 case PAM_ACCT_EXPIRED:
156 /* account expired */
157 _pam_log(LOG_WARNING, "user `%s' account expired", user);
159 case PAM_AUTHTOK_EXPIRED:
160 /* password expired */
161 _pam_log(LOG_WARNING, "user `%s' password expired", user);
163 case PAM_NEW_AUTHTOK_REQD:
164 /* password expired */
165 _pam_log(LOG_WARNING, "user `%s' new password required", user);
167 case PAM_USER_UNKNOWN:
168 /* the user does not exist */
169 if (ctrl & WINBIND_DEBUG_ARG)
170 _pam_log(LOG_NOTICE, "user `%s' not found",
172 if (ctrl & WINBIND_UNKNOWN_OK_ARG) {
177 if (req_type == WINBINDD_PAM_AUTH) {
178 /* Otherwise, the authentication looked good */
179 _pam_log(LOG_NOTICE, "user '%s' granted access", user);
180 } else if (req_type == WINBINDD_PAM_CHAUTHTOK) {
181 /* Otherwise, the authentication looked good */
182 _pam_log(LOG_NOTICE, "user '%s' password changed", user);
184 /* Otherwise, the authentication looked good */
185 _pam_log(LOG_NOTICE, "user '%s' OK", user);
189 /* we don't know anything about this return value */
190 _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'",
196 /* talk to winbindd */
197 static int winbind_auth_request(const char *user, const char *pass, const char *member, int ctrl)
199 struct winbindd_request request;
200 struct winbindd_response response;
202 ZERO_STRUCT(request);
204 strncpy(request.data.auth.user, user,
205 sizeof(request.data.auth.user)-1);
207 strncpy(request.data.auth.pass, pass,
208 sizeof(request.data.auth.pass)-1);
211 return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user);
214 if (!strncmp("S-", member, 2) == 0) {
216 struct winbindd_request request;
217 struct winbindd_response response;
219 ZERO_STRUCT(request);
220 ZERO_STRUCT(response);
222 if (ctrl & WINBIND_DEBUG_ARG)
223 _pam_log(LOG_DEBUG, "no sid given, looking up: %s\n", member);
225 /* fortunatly winbindd can handle non-separated names */
226 strcpy(request.data.name.name, member);
228 if (pam_winbind_request_log(WINBINDD_LOOKUPNAME, &request, &response, ctrl, user)) {
229 _pam_log(LOG_INFO, "could not lookup name: %s\n", member);
233 member = strdup(response.data.sid.sid);
236 strncpy(request.data.auth.required_membership_sid, member,
237 sizeof(request.data.auth.required_membership_sid)-1);
239 return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user);
242 /* talk to winbindd */
243 static int winbind_chauthtok_request(const char *user, const char *oldpass,
244 const char *newpass, int ctrl)
246 struct winbindd_request request;
247 struct winbindd_response response;
249 ZERO_STRUCT(request);
251 if (request.data.chauthtok.user == NULL) return -2;
253 strncpy(request.data.chauthtok.user, user,
254 sizeof(request.data.chauthtok.user) - 1);
256 if (oldpass != NULL) {
257 strncpy(request.data.chauthtok.oldpass, oldpass,
258 sizeof(request.data.chauthtok.oldpass) - 1);
260 request.data.chauthtok.oldpass[0] = '\0';
263 if (newpass != NULL) {
264 strncpy(request.data.chauthtok.newpass, newpass,
265 sizeof(request.data.chauthtok.newpass) - 1);
267 request.data.chauthtok.newpass[0] = '\0';
270 return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK, &request, &response, ctrl, user);
274 * Checks if a user has an account
281 static int valid_user(const char *user)
283 if (getpwnam(user)) return 0;
287 static char *_pam_delete(register char *xx)
295 * obtain a password from the user
298 static int _winbind_read_password(pam_handle_t * pamh
311 * make sure nothing inappropriate gets returned
314 *pass = token = NULL;
317 * which authentication token are we getting?
320 authtok_flag = on(WINBIND__OLD_PASSWORD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK;
323 * should we obtain the password from a PAM item ?
326 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
327 retval = pam_get_item(pamh, authtok_flag, (const void **) &item);
328 if (retval != PAM_SUCCESS) {
331 "pam_get_item returned error to unix-read-password"
334 } else if (item != NULL) { /* we have a password! */
338 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
339 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
340 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
341 && off(WINBIND__OLD_PASSWORD, ctrl)) {
342 return PAM_AUTHTOK_RECOVER_ERR;
346 * getting here implies we will have to get the password from the
351 struct pam_message msg[3], *pmsg[3];
352 struct pam_response *resp;
355 /* prepare to converse */
357 if (comment != NULL) {
359 msg[0].msg_style = PAM_TEXT_INFO;
360 msg[0].msg = comment;
367 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
368 msg[i++].msg = prompt1;
371 if (prompt2 != NULL) {
373 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
374 msg[i++].msg = prompt2;
377 /* so call the conversation expecting i responses */
379 retval = converse(pamh, i, pmsg, &resp);
383 /* interpret the response */
385 if (retval == PAM_SUCCESS) { /* a good conversation */
387 token = x_strdup(resp[i - replies].resp);
391 /* verify that password entered correctly */
392 if (!resp[i - 1].resp
393 || strcmp(token, resp[i - 1].resp)) {
394 _pam_delete(token); /* mistyped */
395 retval = PAM_AUTHTOK_RECOVER_ERR;
396 _make_remark(pamh ,PAM_ERROR_MSG, MISTYPED_PASS);
401 ,"could not recover authentication token");
406 * tidy up the conversation (resp_retcode) is ignored
407 * -- what is it for anyway? AGM
410 _pam_drop_reply(resp, i);
413 retval = (retval == PAM_SUCCESS)
414 ? PAM_AUTHTOK_RECOVER_ERR : retval;
418 if (retval != PAM_SUCCESS) {
419 if (on(WINBIND_DEBUG_ARG, ctrl))
421 "unable to obtain a password");
424 /* 'token' is the entered password */
426 /* we store this password as an item */
428 retval = pam_set_item(pamh, authtok_flag, token);
429 _pam_delete(token); /* clean it up */
430 if (retval != PAM_SUCCESS
431 || (retval = pam_get_item(pamh, authtok_flag
432 ,(const void **) &item))
435 _pam_log(LOG_CRIT, "error manipulating password");
441 item = NULL; /* break link to password */
447 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
448 int argc, const char **argv)
450 const char *username;
451 const char *password;
452 const char *member = NULL;
453 int retval = PAM_AUTH_ERR;
456 /* parse arguments */
457 int ctrl = _pam_parse(argc, argv);
459 /* Get the username */
460 retval = pam_get_user(pamh, &username, NULL);
461 if ((retval != PAM_SUCCESS) || (!username)) {
462 if (ctrl & WINBIND_DEBUG_ARG)
463 _pam_log(LOG_DEBUG,"can not get the username");
464 return PAM_SERVICE_ERR;
467 retval = _winbind_read_password(pamh, ctrl, NULL,
471 if (retval != PAM_SUCCESS) {
472 _pam_log(LOG_ERR, "Could not retrieve user's password");
473 return PAM_AUTHTOK_ERR;
476 if (ctrl & WINBIND_DEBUG_ARG) {
478 /* Let's not give too much away in the log file */
480 #ifdef DEBUG_PASSWORD
481 _pam_log(LOG_INFO, "Verify user `%s' with password `%s'",
484 _pam_log(LOG_INFO, "Verify user `%s'", username);
488 /* Retrieve membership-string here */
489 for ( i=0; i<argc; i++ ) {
491 if (!strncmp(argv[i], "required_membership", strlen("required_membership"))) {
494 char *parm = strdup(argv[i]);
496 if ( (p = strchr( parm, '=' )) == NULL) {
497 _pam_log(LOG_INFO, "no \"=\" delimiter for \"required_membership\" found\n");
501 member = strdup(p+1);
505 /* Now use the username to look up password */
506 return winbind_auth_request(username, password, member, ctrl);
510 int pam_sm_setcred(pam_handle_t *pamh, int flags,
511 int argc, const char **argv)
517 * Account management. We want to verify that the account exists
518 * before returning PAM_SUCCESS
521 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
522 int argc, const char **argv)
524 const char *username;
525 int retval = PAM_USER_UNKNOWN;
527 /* parse arguments */
528 int ctrl = _pam_parse(argc, argv);
530 /* Get the username */
531 retval = pam_get_user(pamh, &username, NULL);
532 if ((retval != PAM_SUCCESS) || (!username)) {
533 if (ctrl & WINBIND_DEBUG_ARG)
534 _pam_log(LOG_DEBUG,"can not get the username");
535 return PAM_SERVICE_ERR;
538 /* Verify the username */
539 retval = valid_user(username);
542 /* some sort of system error. The log was already printed */
543 return PAM_SERVICE_ERR;
545 /* the user does not exist */
546 if (ctrl & WINBIND_DEBUG_ARG)
547 _pam_log(LOG_NOTICE, "user `%s' not found",
549 if (ctrl & WINBIND_UNKNOWN_OK_ARG)
551 return PAM_USER_UNKNOWN;
553 /* Otherwise, the authentication looked good */
554 _pam_log(LOG_NOTICE, "user '%s' granted access", username);
557 /* we don't know anything about this return value */
558 _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'",
560 return PAM_SERVICE_ERR;
563 /* should not be reached */
567 int pam_sm_open_session(pam_handle_t *pamh, int flags,
568 int argc, const char **argv)
570 /* parse arguments */
571 int ctrl = _pam_parse(argc, argv);
572 if (ctrl & WINBIND_DEBUG_ARG)
573 _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_open_session handler");
577 int pam_sm_close_session(pam_handle_t *pamh, int flags,
578 int argc, const char **argv)
580 /* parse arguments */
581 int ctrl = _pam_parse(argc, argv);
582 if (ctrl & WINBIND_DEBUG_ARG)
583 _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler");
589 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
590 int argc, const char **argv)
594 unsigned int ctrl = _pam_parse(argc, argv);
596 /* <DO NOT free() THESE> */
598 const char *member = NULL;
599 char *pass_old, *pass_new;
600 /* </DO NOT free() THESE> */
607 * First get the name of a user
609 retval = pam_get_user(pamh, &user, "Username: ");
610 if (retval == PAM_SUCCESS) {
612 _pam_log(LOG_ERR, "username was NULL!");
613 return PAM_USER_UNKNOWN;
615 if (retval == PAM_SUCCESS && on(WINBIND_DEBUG_ARG, ctrl))
616 _pam_log(LOG_DEBUG, "username [%s] obtained",
619 if (on(WINBIND_DEBUG_ARG, ctrl))
621 "password - could not identify user");
626 * obtain and verify the current password (OLDAUTHTOK) for
630 if (flags & PAM_PRELIM_CHECK) {
632 /* instruct user what is happening */
633 #define greeting "Changing password for "
634 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
635 if (Announce == NULL) {
637 "password - out of memory");
640 (void) strcpy(Announce, greeting);
641 (void) strcpy(Announce + sizeof(greeting) - 1, user);
644 lctrl = ctrl | WINBIND__OLD_PASSWORD;
645 retval = _winbind_read_password(pamh, lctrl
647 ,"(current) NT password: "
649 ,(const char **) &pass_old);
652 if (retval != PAM_SUCCESS) {
654 ,"password - (old) token not obtained");
657 /* verify that this is the password for this user */
659 retval = winbind_auth_request(user, pass_old, member, ctrl);
661 if (retval != PAM_ACCT_EXPIRED
662 && retval != PAM_AUTHTOK_EXPIRED
663 && retval != PAM_NEW_AUTHTOK_REQD
664 && retval != PAM_SUCCESS) {
669 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
671 if (retval != PAM_SUCCESS) {
673 "failed to set PAM_OLDAUTHTOK");
675 } else if (flags & PAM_UPDATE_AUTHTOK) {
678 * obtain the proposed password
682 * get the old token back.
685 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
686 ,(const void **) &pass_old);
688 if (retval != PAM_SUCCESS) {
689 _pam_log(LOG_NOTICE, "user not authenticated");
695 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
696 ctrl = WINBIND_USE_FIRST_PASS_ARG | lctrl;
699 retval = PAM_AUTHTOK_ERR;
700 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
702 * use_authtok is to force the use of a previously entered
703 * password -- needed for pluggable password strength checking
706 retval = _winbind_read_password(pamh, lctrl
708 ,"Enter new NT password: "
709 ,"Retype new NT password: "
710 ,(const char **) &pass_new);
712 if (retval != PAM_SUCCESS) {
713 if (on(WINBIND_DEBUG_ARG, ctrl)) {
715 ,"password - new password not obtained");
717 pass_old = NULL;/* tidy up */
722 * At this point we know who the user is and what they
723 * propose as their new password. Verify that the new
724 * password is acceptable.
727 if (pass_new[0] == '\0') {/* "\0" password = NULL */
733 * By reaching here we have approved the passwords and must now
734 * rebuild the password database file.
737 retval = winbind_chauthtok_request(user, pass_old, pass_new, ctrl);
738 _pam_overwrite(pass_new);
739 _pam_overwrite(pass_old);
740 pass_old = pass_new = NULL;
742 retval = PAM_SERVICE_ERR;
750 /* static module data */
752 struct pam_module _pam_winbind_modstruct = {
758 pam_sm_close_session,
765 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
766 * Copyright (c) Tim Potter <tpot@samba.org> 2000
767 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
768 * Copyright (c) Jan Rêkorajski 1999.
769 * Copyright (c) Andrew G. Morgan 1996-8.
770 * Copyright (c) Alex O. Yuriev, 1996.
771 * Copyright (c) Cristian Gafton 1996.
772 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
774 * Redistribution and use in source and binary forms, with or without
775 * modification, are permitted provided that the following conditions
777 * 1. Redistributions of source code must retain the above copyright
778 * notice, and the entire permission notice in its entirety,
779 * including the disclaimer of warranties.
780 * 2. Redistributions in binary form must reproduce the above copyright
781 * notice, this list of conditions and the following disclaimer in the
782 * documentation and/or other materials provided with the distribution.
783 * 3. The name of the author may not be used to endorse or promote
784 * products derived from this software without specific prior
785 * written permission.
787 * ALTERNATIVELY, this product may be distributed under the terms of
788 * the GNU Public License, in which case the provisions of the GPL are
789 * required INSTEAD OF the above restrictions. (This clause is
790 * necessary due to a potential bad interaction between the GPL and
791 * the restrictions contained in a BSD-style copyright.)
793 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
794 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
795 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
796 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
797 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
798 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
799 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
800 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
801 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
802 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
803 * OF THE POSSIBILITY OF SUCH DAMAGE.