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 Cristian 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, "require_membership_of", strlen("require_membership_of")))
49 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
50 else if (!strncasecmp(*argv, "require-membership-of", strlen("require-membership-of")))
51 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
53 _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv);
60 static void _pam_winbind_cleanup_func(pam_handle_t *pamh, void *data, int error_status)
65 /* --- authentication management functions --- */
67 /* Attempt a conversation */
69 static int converse(pam_handle_t *pamh, int nargs,
70 struct pam_message **message,
71 struct pam_response **response)
74 struct pam_conv *conv;
76 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ;
77 if (retval == PAM_SUCCESS) {
78 retval = conv->conv(nargs, (const struct pam_message **)message,
79 response, conv->appdata_ptr);
82 return retval; /* propagate error status */
86 static int _make_remark(pam_handle_t * pamh, int type, const char *text)
88 int retval = PAM_SUCCESS;
90 struct pam_message *pmsg[1], msg[1];
91 struct pam_response *resp;
95 msg[0].msg_style = type;
98 retval = converse(pamh, 1, pmsg, &resp);
101 _pam_drop_reply(resp, 1);
106 static int pam_winbind_request(enum winbindd_cmd req_type,
107 struct winbindd_request *request,
108 struct winbindd_response *response)
111 /* Fill in request and send down pipe */
112 init_request(request, req_type);
114 if (write_sock(request, sizeof(*request), 0) == -1) {
115 _pam_log(LOG_ERR, "write to socket failed!");
117 return PAM_SERVICE_ERR;
121 if (read_reply(response) == -1) {
122 _pam_log(LOG_ERR, "read from socket failed!");
124 return PAM_SERVICE_ERR;
127 /* We are done with the socket - close it and avoid mischeif */
130 /* Copy reply data from socket */
131 if (response->result != WINBINDD_OK) {
132 if (response->data.auth.pam_error != PAM_SUCCESS) {
133 _pam_log(LOG_ERR, "request failed: %s, PAM error was %d, NT error was %s",
134 response->data.auth.error_string,
135 response->data.auth.pam_error,
136 response->data.auth.nt_status_string);
137 return response->data.auth.pam_error;
139 _pam_log(LOG_ERR, "request failed, but PAM error 0!");
140 return PAM_SERVICE_ERR;
147 static int pam_winbind_request_log(enum winbindd_cmd req_type,
148 struct winbindd_request *request,
149 struct winbindd_response *response,
155 retval = pam_winbind_request(req_type, request, response);
159 /* incorrect password */
160 _pam_log(LOG_WARNING, "user `%s' denied access (incorrect password or invalid membership)", user);
162 case PAM_ACCT_EXPIRED:
163 /* account expired */
164 _pam_log(LOG_WARNING, "user `%s' account expired", user);
166 case PAM_AUTHTOK_EXPIRED:
167 /* password expired */
168 _pam_log(LOG_WARNING, "user `%s' password expired", user);
170 case PAM_NEW_AUTHTOK_REQD:
171 /* password expired */
172 _pam_log(LOG_WARNING, "user `%s' new password required", user);
174 case PAM_USER_UNKNOWN:
175 /* the user does not exist */
176 if (ctrl & WINBIND_DEBUG_ARG)
177 _pam_log(LOG_NOTICE, "user `%s' not found",
179 if (ctrl & WINBIND_UNKNOWN_OK_ARG) {
184 if (req_type == WINBINDD_PAM_AUTH) {
185 /* Otherwise, the authentication looked good */
186 _pam_log(LOG_NOTICE, "user '%s' granted access", user);
187 } else if (req_type == WINBINDD_PAM_CHAUTHTOK) {
188 /* Otherwise, the authentication looked good */
189 _pam_log(LOG_NOTICE, "user '%s' password changed", user);
191 /* Otherwise, the authentication looked good */
192 _pam_log(LOG_NOTICE, "user '%s' OK", user);
196 /* we don't know anything about this return value */
197 _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s')",
203 /* talk to winbindd */
204 static int winbind_auth_request(const char *user, const char *pass, const char *member, int ctrl)
206 struct winbindd_request request;
207 struct winbindd_response response;
209 ZERO_STRUCT(request);
211 strncpy(request.data.auth.user, user,
212 sizeof(request.data.auth.user)-1);
214 strncpy(request.data.auth.pass, pass,
215 sizeof(request.data.auth.pass)-1);
218 return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user);
221 if (!strncmp("S-", member, 2) == 0) {
223 struct winbindd_request sid_request;
224 struct winbindd_response sid_response;
226 ZERO_STRUCT(sid_request);
227 ZERO_STRUCT(sid_response);
229 if (ctrl & WINBIND_DEBUG_ARG)
230 _pam_log(LOG_DEBUG, "no sid given, looking up: %s\n", member);
232 /* fortunatly winbindd can handle non-separated names */
233 strcpy(sid_request.data.name.name, member);
235 if (pam_winbind_request_log(WINBINDD_LOOKUPNAME, &sid_request, &sid_response, ctrl, user)) {
236 _pam_log(LOG_INFO, "could not lookup name: %s\n", member);
240 member = sid_response.data.sid.sid;
243 strncpy(request.data.auth.require_membership_of_sid, member,
244 sizeof(request.data.auth.require_membership_of_sid)-1);
246 return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user);
249 /* talk to winbindd */
250 static int winbind_chauthtok_request(const char *user, const char *oldpass,
251 const char *newpass, int ctrl)
253 struct winbindd_request request;
254 struct winbindd_response response;
256 ZERO_STRUCT(request);
258 if (request.data.chauthtok.user == NULL) return -2;
260 strncpy(request.data.chauthtok.user, user,
261 sizeof(request.data.chauthtok.user) - 1);
263 if (oldpass != NULL) {
264 strncpy(request.data.chauthtok.oldpass, oldpass,
265 sizeof(request.data.chauthtok.oldpass) - 1);
267 request.data.chauthtok.oldpass[0] = '\0';
270 if (newpass != NULL) {
271 strncpy(request.data.chauthtok.newpass, newpass,
272 sizeof(request.data.chauthtok.newpass) - 1);
274 request.data.chauthtok.newpass[0] = '\0';
277 return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK, &request, &response, ctrl, user);
281 * Checks if a user has an account
288 static int valid_user(const char *user)
290 if (getpwnam(user)) return 0;
294 static char *_pam_delete(register char *xx)
302 * obtain a password from the user
305 static int _winbind_read_password(pam_handle_t * pamh
318 * make sure nothing inappropriate gets returned
321 *pass = token = NULL;
324 * which authentication token are we getting?
327 authtok_flag = on(WINBIND__OLD_PASSWORD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK;
330 * should we obtain the password from a PAM item ?
333 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
334 retval = pam_get_item(pamh, authtok_flag, (const void **) &item);
335 if (retval != PAM_SUCCESS) {
338 "pam_get_item returned error to unix-read-password"
341 } else if (item != NULL) { /* we have a password! */
345 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
346 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
347 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
348 && off(WINBIND__OLD_PASSWORD, ctrl)) {
349 return PAM_AUTHTOK_RECOVER_ERR;
353 * getting here implies we will have to get the password from the
358 struct pam_message msg[3], *pmsg[3];
359 struct pam_response *resp;
362 /* prepare to converse */
364 if (comment != NULL) {
366 msg[0].msg_style = PAM_TEXT_INFO;
367 msg[0].msg = comment;
374 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
375 msg[i++].msg = prompt1;
378 if (prompt2 != NULL) {
380 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
381 msg[i++].msg = prompt2;
384 /* so call the conversation expecting i responses */
386 retval = converse(pamh, i, pmsg, &resp);
390 /* interpret the response */
392 if (retval == PAM_SUCCESS) { /* a good conversation */
394 token = x_strdup(resp[i - replies].resp);
398 /* verify that password entered correctly */
399 if (!resp[i - 1].resp
400 || strcmp(token, resp[i - 1].resp)) {
401 _pam_delete(token); /* mistyped */
402 retval = PAM_AUTHTOK_RECOVER_ERR;
403 _make_remark(pamh ,PAM_ERROR_MSG, MISTYPED_PASS);
408 ,"could not recover authentication token");
413 * tidy up the conversation (resp_retcode) is ignored
414 * -- what is it for anyway? AGM
417 _pam_drop_reply(resp, i);
420 retval = (retval == PAM_SUCCESS)
421 ? PAM_AUTHTOK_RECOVER_ERR : retval;
425 if (retval != PAM_SUCCESS) {
426 if (on(WINBIND_DEBUG_ARG, ctrl))
428 "unable to obtain a password");
431 /* 'token' is the entered password */
433 /* we store this password as an item */
435 retval = pam_set_item(pamh, authtok_flag, token);
436 _pam_delete(token); /* clean it up */
437 if (retval != PAM_SUCCESS
438 || (retval = pam_get_item(pamh, authtok_flag
439 ,(const void **) &item))
442 _pam_log(LOG_CRIT, "error manipulating password");
448 item = NULL; /* break link to password */
454 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
455 int argc, const char **argv)
457 const char *username;
458 const char *password;
459 const char *member = NULL;
460 int retval = PAM_AUTH_ERR;
463 /* parse arguments */
464 int ctrl = _pam_parse(argc, argv);
466 /* Get the username */
467 retval = pam_get_user(pamh, &username, NULL);
468 if ((retval != PAM_SUCCESS) || (!username)) {
469 if (ctrl & WINBIND_DEBUG_ARG)
470 _pam_log(LOG_DEBUG,"can not get the username");
471 return PAM_SERVICE_ERR;
474 retval = _winbind_read_password(pamh, ctrl, NULL,
478 if (retval != PAM_SUCCESS) {
479 _pam_log(LOG_ERR, "Could not retrieve user's password");
480 return PAM_AUTHTOK_ERR;
483 if (ctrl & WINBIND_DEBUG_ARG) {
485 /* Let's not give too much away in the log file */
487 #ifdef DEBUG_PASSWORD
488 _pam_log(LOG_INFO, "Verify user `%s' with password `%s'",
491 _pam_log(LOG_INFO, "Verify user `%s'", username);
495 if (ctrl & WINBIND_REQUIRED_MEMBERSHIP) {
497 for ( i=0; i<argc; i++ ) {
499 if ((strncmp(argv[i], "require_membership_of", strlen("require_membership_of")) == 0) ||
500 (strncmp(argv[i], "require-membership-of", strlen("require-membership-of")) == 0)) {
503 char *parm = strdup(argv[i]);
505 if ( (p = strchr( parm, '=' )) == NULL) {
506 _pam_log(LOG_INFO, "no \"=\" delimiter for \"require_membership_of\" found\n");
510 member = strdup(p+1);
515 /* Now use the username to look up password */
516 retval = winbind_auth_request(username, password, member, ctrl);
517 if (retval == PAM_NEW_AUTHTOK_REQD ||
518 retval == PAM_AUTHTOK_EXPIRED) {
522 if (!asprintf(&buf, "%d", retval)) {
526 pam_set_data( pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, (void *)buf, _pam_winbind_cleanup_func);
535 int pam_sm_setcred(pam_handle_t *pamh, int flags,
536 int argc, const char **argv)
542 * Account management. We want to verify that the account exists
543 * before returning PAM_SUCCESS
546 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
547 int argc, const char **argv)
549 const char *username;
552 int retval = PAM_USER_UNKNOWN;
554 /* parse arguments */
555 int ctrl = _pam_parse(argc, argv);
557 /* Get the username */
558 retval = pam_get_user(pamh, &username, NULL);
559 if ((retval != PAM_SUCCESS) || (!username)) {
560 if (ctrl & WINBIND_DEBUG_ARG)
561 _pam_log(LOG_DEBUG,"can not get the username");
562 return PAM_SERVICE_ERR;
565 /* Verify the username */
566 retval = valid_user(username);
569 /* some sort of system error. The log was already printed */
570 return PAM_SERVICE_ERR;
572 /* the user does not exist */
573 if (ctrl & WINBIND_DEBUG_ARG)
574 _pam_log(LOG_NOTICE, "user `%s' not found",
576 if (ctrl & WINBIND_UNKNOWN_OK_ARG)
578 return PAM_USER_UNKNOWN;
580 pam_get_data( pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, (const void **)&tmp);
585 case PAM_AUTHTOK_EXPIRED:
586 /* fall through, since new token is required in this case */
587 case PAM_NEW_AUTHTOK_REQD:
588 _pam_log(LOG_WARNING, "pam_sm_acct_mgmt success but %s is set",
589 PAM_WINBIND_NEW_AUTHTOK_REQD);
590 _pam_log(LOG_NOTICE, "user '%s' needs new password", username);
591 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
592 return PAM_NEW_AUTHTOK_REQD;
594 _pam_log(LOG_WARNING, "pam_sm_acct_mgmt success");
595 _pam_log(LOG_NOTICE, "user '%s' granted access", username);
600 /* Otherwise, the authentication looked good */
601 _pam_log(LOG_NOTICE, "user '%s' granted access", username);
604 /* we don't know anything about this return value */
605 _pam_log(LOG_ERR, "internal module error (retval = %d, user = `%s'",
607 return PAM_SERVICE_ERR;
610 /* should not be reached */
614 int pam_sm_open_session(pam_handle_t *pamh, int flags,
615 int argc, const char **argv)
617 /* parse arguments */
618 int ctrl = _pam_parse(argc, argv);
619 if (ctrl & WINBIND_DEBUG_ARG)
620 _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_open_session handler");
624 int pam_sm_close_session(pam_handle_t *pamh, int flags,
625 int argc, const char **argv)
627 /* parse arguments */
628 int ctrl = _pam_parse(argc, argv);
629 if (ctrl & WINBIND_DEBUG_ARG)
630 _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler");
636 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
637 int argc, const char **argv)
641 unsigned int ctrl = _pam_parse(argc, argv);
643 /* <DO NOT free() THESE> */
645 const char *member = NULL;
646 char *pass_old, *pass_new;
647 /* </DO NOT free() THESE> */
654 * First get the name of a user
656 retval = pam_get_user(pamh, &user, "Username: ");
657 if (retval == PAM_SUCCESS) {
659 _pam_log(LOG_ERR, "username was NULL!");
660 return PAM_USER_UNKNOWN;
662 if (retval == PAM_SUCCESS && on(WINBIND_DEBUG_ARG, ctrl))
663 _pam_log(LOG_DEBUG, "username [%s] obtained",
666 if (on(WINBIND_DEBUG_ARG, ctrl))
668 "password - could not identify user");
673 * obtain and verify the current password (OLDAUTHTOK) for
677 if (flags & PAM_PRELIM_CHECK) {
679 /* instruct user what is happening */
680 #define greeting "Changing password for "
681 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
682 if (Announce == NULL) {
684 "password - out of memory");
687 (void) strcpy(Announce, greeting);
688 (void) strcpy(Announce + sizeof(greeting) - 1, user);
691 lctrl = ctrl | WINBIND__OLD_PASSWORD;
692 retval = _winbind_read_password(pamh, lctrl
694 ,"(current) NT password: "
696 ,(const char **) &pass_old);
699 if (retval != PAM_SUCCESS) {
701 ,"password - (old) token not obtained");
704 /* verify that this is the password for this user */
706 retval = winbind_auth_request(user, pass_old, member, ctrl);
708 if (retval != PAM_ACCT_EXPIRED
709 && retval != PAM_AUTHTOK_EXPIRED
710 && retval != PAM_NEW_AUTHTOK_REQD
711 && retval != PAM_SUCCESS) {
716 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
718 if (retval != PAM_SUCCESS) {
720 "failed to set PAM_OLDAUTHTOK");
722 } else if (flags & PAM_UPDATE_AUTHTOK) {
725 * obtain the proposed password
729 * get the old token back.
732 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
733 ,(const void **) &pass_old);
735 if (retval != PAM_SUCCESS) {
736 _pam_log(LOG_NOTICE, "user not authenticated");
742 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
743 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
746 retval = PAM_AUTHTOK_ERR;
747 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
749 * use_authtok is to force the use of a previously entered
750 * password -- needed for pluggable password strength checking
753 retval = _winbind_read_password(pamh, lctrl
755 ,"Enter new NT password: "
756 ,"Retype new NT password: "
757 ,(const char **) &pass_new);
759 if (retval != PAM_SUCCESS) {
760 if (on(WINBIND_DEBUG_ARG, ctrl)) {
762 ,"password - new password not obtained");
764 pass_old = NULL;/* tidy up */
769 * At this point we know who the user is and what they
770 * propose as their new password. Verify that the new
771 * password is acceptable.
774 if (pass_new[0] == '\0') {/* "\0" password = NULL */
780 * By reaching here we have approved the passwords and must now
781 * rebuild the password database file.
784 retval = winbind_chauthtok_request(user, pass_old, pass_new, ctrl);
785 _pam_overwrite(pass_new);
786 _pam_overwrite(pass_old);
787 pass_old = pass_new = NULL;
789 retval = PAM_SERVICE_ERR;
797 /* static module data */
799 struct pam_module _pam_winbind_modstruct = {
805 pam_sm_close_session,
812 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
813 * Copyright (c) Tim Potter <tpot@samba.org> 2000
814 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
815 * Copyright (c) Jan Rêkorajski 1999.
816 * Copyright (c) Andrew G. Morgan 1996-8.
817 * Copyright (c) Alex O. Yuriev, 1996.
818 * Copyright (c) Cristian Gafton 1996.
819 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
821 * Redistribution and use in source and binary forms, with or without
822 * modification, are permitted provided that the following conditions
824 * 1. Redistributions of source code must retain the above copyright
825 * notice, and the entire permission notice in its entirety,
826 * including the disclaimer of warranties.
827 * 2. Redistributions in binary form must reproduce the above copyright
828 * notice, this list of conditions and the following disclaimer in the
829 * documentation and/or other materials provided with the distribution.
830 * 3. The name of the author may not be used to endorse or promote
831 * products derived from this software without specific prior
832 * written permission.
834 * ALTERNATIVELY, this product may be distributed under the terms of
835 * the GNU Public License, in which case the provisions of the GPL are
836 * required INSTEAD OF the above restrictions. (This clause is
837 * necessary due to a potential bad interaction between the GPL and
838 * the restrictions contained in a BSD-style copyright.)
840 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
841 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
842 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
843 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
844 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
845 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
846 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
847 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
848 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
849 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
850 * OF THE POSSIBILITY OF SUCH DAMAGE.