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
, "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 /* --- authentication management functions --- */
62 /* Attempt a conversation */
64 static int converse(pam_handle_t
*pamh
, int nargs
,
65 struct pam_message
**message
,
66 struct pam_response
**response
)
69 struct pam_conv
*conv
;
71 retval
= pam_get_item(pamh
, PAM_CONV
, (const void **) &conv
) ;
72 if (retval
== PAM_SUCCESS
) {
73 retval
= conv
->conv(nargs
, (const struct pam_message
**)message
,
74 response
, conv
->appdata_ptr
);
77 return retval
; /* propagate error status */
81 static int _make_remark(pam_handle_t
* pamh
, int type
, const char *text
)
83 int retval
= PAM_SUCCESS
;
85 struct pam_message
*pmsg
[1], msg
[1];
86 struct pam_response
*resp
;
90 msg
[0].msg_style
= type
;
93 retval
= converse(pamh
, 1, pmsg
, &resp
);
96 _pam_drop_reply(resp
, 1);
101 static int pam_winbind_request(enum winbindd_cmd req_type
,
102 struct winbindd_request
*request
,
103 struct winbindd_response
*response
)
106 /* Fill in request and send down pipe */
107 init_request(request
, req_type
);
109 if (write_sock(request
, sizeof(*request
)) == -1) {
110 _pam_log(LOG_ERR
, "write to socket failed!");
112 return PAM_SERVICE_ERR
;
116 if (read_reply(response
) == -1) {
117 _pam_log(LOG_ERR
, "read from socket failed!");
119 return PAM_SERVICE_ERR
;
122 /* We are done with the socket - close it and avoid mischeif */
125 /* Copy reply data from socket */
126 if (response
->result
!= WINBINDD_OK
) {
127 if (response
->data
.auth
.pam_error
!= PAM_SUCCESS
) {
128 _pam_log(LOG_ERR
, "request failed: %s, PAM error was %d, NT error was %s",
129 response
->data
.auth
.error_string
,
130 response
->data
.auth
.pam_error
,
131 response
->data
.auth
.nt_status_string
);
132 return response
->data
.auth
.pam_error
;
134 _pam_log(LOG_ERR
, "request failed, but PAM error 0!");
135 return PAM_SERVICE_ERR
;
142 static int pam_winbind_request_log(enum winbindd_cmd req_type
,
143 struct winbindd_request
*request
,
144 struct winbindd_response
*response
,
150 retval
= pam_winbind_request(req_type
, request
, response
);
154 /* incorrect password */
155 _pam_log(LOG_WARNING
, "user `%s' denied access (incorrect password or invalid membership)", user
);
157 case PAM_ACCT_EXPIRED
:
158 /* account expired */
159 _pam_log(LOG_WARNING
, "user `%s' account expired", user
);
161 case PAM_AUTHTOK_EXPIRED
:
162 /* password expired */
163 _pam_log(LOG_WARNING
, "user `%s' password expired", user
);
165 case PAM_NEW_AUTHTOK_REQD
:
166 /* password expired */
167 _pam_log(LOG_WARNING
, "user `%s' new password required", user
);
169 case PAM_USER_UNKNOWN
:
170 /* the user does not exist */
171 if (ctrl
& WINBIND_DEBUG_ARG
)
172 _pam_log(LOG_NOTICE
, "user `%s' not found",
174 if (ctrl
& WINBIND_UNKNOWN_OK_ARG
) {
179 if (req_type
== WINBINDD_PAM_AUTH
) {
180 /* Otherwise, the authentication looked good */
181 _pam_log(LOG_NOTICE
, "user '%s' granted access", user
);
182 } else if (req_type
== WINBINDD_PAM_CHAUTHTOK
) {
183 /* Otherwise, the authentication looked good */
184 _pam_log(LOG_NOTICE
, "user '%s' password changed", user
);
186 /* Otherwise, the authentication looked good */
187 _pam_log(LOG_NOTICE
, "user '%s' OK", user
);
191 /* we don't know anything about this return value */
192 _pam_log(LOG_ERR
, "internal module error (retval = %d, user = `%s'",
198 /* talk to winbindd */
199 static int winbind_auth_request(const char *user
, const char *pass
, const char *member
, int ctrl
)
201 struct winbindd_request request
;
202 struct winbindd_response response
;
204 ZERO_STRUCT(request
);
206 strncpy(request
.data
.auth
.user
, user
,
207 sizeof(request
.data
.auth
.user
)-1);
209 strncpy(request
.data
.auth
.pass
, pass
,
210 sizeof(request
.data
.auth
.pass
)-1);
213 return pam_winbind_request_log(WINBINDD_PAM_AUTH
, &request
, &response
, ctrl
, user
);
216 if (!strncmp("S-", member
, 2) == 0) {
218 struct winbindd_request sid_request
;
219 struct winbindd_response sid_response
;
221 ZERO_STRUCT(sid_request
);
222 ZERO_STRUCT(sid_response
);
224 if (ctrl
& WINBIND_DEBUG_ARG
)
225 _pam_log(LOG_DEBUG
, "no sid given, looking up: %s\n", member
);
227 /* fortunatly winbindd can handle non-separated names */
228 strcpy(sid_request
.data
.name
.name
, member
);
230 if (pam_winbind_request_log(WINBINDD_LOOKUPNAME
, &sid_request
, &sid_response
, ctrl
, user
)) {
231 _pam_log(LOG_INFO
, "could not lookup name: %s\n", member
);
235 member
= sid_response
.data
.sid
.sid
;
238 strncpy(request
.data
.auth
.require_membership_of_sid
, member
,
239 sizeof(request
.data
.auth
.require_membership_of_sid
)-1);
241 return pam_winbind_request_log(WINBINDD_PAM_AUTH
, &request
, &response
, ctrl
, user
);
244 /* talk to winbindd */
245 static int winbind_chauthtok_request(const char *user
, const char *oldpass
,
246 const char *newpass
, int ctrl
)
248 struct winbindd_request request
;
249 struct winbindd_response response
;
251 ZERO_STRUCT(request
);
253 if (request
.data
.chauthtok
.user
== NULL
) return -2;
255 strncpy(request
.data
.chauthtok
.user
, user
,
256 sizeof(request
.data
.chauthtok
.user
) - 1);
258 if (oldpass
!= NULL
) {
259 strncpy(request
.data
.chauthtok
.oldpass
, oldpass
,
260 sizeof(request
.data
.chauthtok
.oldpass
) - 1);
262 request
.data
.chauthtok
.oldpass
[0] = '\0';
265 if (newpass
!= NULL
) {
266 strncpy(request
.data
.chauthtok
.newpass
, newpass
,
267 sizeof(request
.data
.chauthtok
.newpass
) - 1);
269 request
.data
.chauthtok
.newpass
[0] = '\0';
272 return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK
, &request
, &response
, ctrl
, user
);
276 * Checks if a user has an account
283 static int valid_user(const char *user
)
285 if (getpwnam(user
)) return 0;
289 static char *_pam_delete(register char *xx
)
297 * obtain a password from the user
300 static int _winbind_read_password(pam_handle_t
* pamh
313 * make sure nothing inappropriate gets returned
316 *pass
= token
= NULL
;
319 * which authentication token are we getting?
322 authtok_flag
= on(WINBIND__OLD_PASSWORD
, ctrl
) ? PAM_OLDAUTHTOK
: PAM_AUTHTOK
;
325 * should we obtain the password from a PAM item ?
328 if (on(WINBIND_TRY_FIRST_PASS_ARG
, ctrl
) || on(WINBIND_USE_FIRST_PASS_ARG
, ctrl
)) {
329 retval
= pam_get_item(pamh
, authtok_flag
, (const void **) &item
);
330 if (retval
!= PAM_SUCCESS
) {
333 "pam_get_item returned error to unix-read-password"
336 } else if (item
!= NULL
) { /* we have a password! */
340 } else if (on(WINBIND_USE_FIRST_PASS_ARG
, ctrl
)) {
341 return PAM_AUTHTOK_RECOVER_ERR
; /* didn't work */
342 } else if (on(WINBIND_USE_AUTHTOK_ARG
, ctrl
)
343 && off(WINBIND__OLD_PASSWORD
, ctrl
)) {
344 return PAM_AUTHTOK_RECOVER_ERR
;
348 * getting here implies we will have to get the password from the
353 struct pam_message msg
[3], *pmsg
[3];
354 struct pam_response
*resp
;
357 /* prepare to converse */
359 if (comment
!= NULL
) {
361 msg
[0].msg_style
= PAM_TEXT_INFO
;
362 msg
[0].msg
= comment
;
369 msg
[i
].msg_style
= PAM_PROMPT_ECHO_OFF
;
370 msg
[i
++].msg
= prompt1
;
373 if (prompt2
!= NULL
) {
375 msg
[i
].msg_style
= PAM_PROMPT_ECHO_OFF
;
376 msg
[i
++].msg
= prompt2
;
379 /* so call the conversation expecting i responses */
381 retval
= converse(pamh
, i
, pmsg
, &resp
);
385 /* interpret the response */
387 if (retval
== PAM_SUCCESS
) { /* a good conversation */
389 token
= x_strdup(resp
[i
- replies
].resp
);
393 /* verify that password entered correctly */
394 if (!resp
[i
- 1].resp
395 || strcmp(token
, resp
[i
- 1].resp
)) {
396 _pam_delete(token
); /* mistyped */
397 retval
= PAM_AUTHTOK_RECOVER_ERR
;
398 _make_remark(pamh
,PAM_ERROR_MSG
, MISTYPED_PASS
);
403 ,"could not recover authentication token");
408 * tidy up the conversation (resp_retcode) is ignored
409 * -- what is it for anyway? AGM
412 _pam_drop_reply(resp
, i
);
415 retval
= (retval
== PAM_SUCCESS
)
416 ? PAM_AUTHTOK_RECOVER_ERR
: retval
;
420 if (retval
!= PAM_SUCCESS
) {
421 if (on(WINBIND_DEBUG_ARG
, ctrl
))
423 "unable to obtain a password");
426 /* 'token' is the entered password */
428 /* we store this password as an item */
430 retval
= pam_set_item(pamh
, authtok_flag
, token
);
431 _pam_delete(token
); /* clean it up */
432 if (retval
!= PAM_SUCCESS
433 || (retval
= pam_get_item(pamh
, authtok_flag
434 ,(const void **) &item
))
437 _pam_log(LOG_CRIT
, "error manipulating password");
443 item
= NULL
; /* break link to password */
449 int pam_sm_authenticate(pam_handle_t
*pamh
, int flags
,
450 int argc
, const char **argv
)
452 const char *username
;
453 const char *password
;
454 const char *member
= NULL
;
455 int retval
= PAM_AUTH_ERR
;
458 /* parse arguments */
459 int ctrl
= _pam_parse(argc
, argv
);
461 /* Get the username */
462 retval
= pam_get_user(pamh
, &username
, NULL
);
463 if ((retval
!= PAM_SUCCESS
) || (!username
)) {
464 if (ctrl
& WINBIND_DEBUG_ARG
)
465 _pam_log(LOG_DEBUG
,"can not get the username");
466 return PAM_SERVICE_ERR
;
469 retval
= _winbind_read_password(pamh
, ctrl
, NULL
,
473 if (retval
!= PAM_SUCCESS
) {
474 _pam_log(LOG_ERR
, "Could not retrieve user's password");
475 return PAM_AUTHTOK_ERR
;
478 if (ctrl
& WINBIND_DEBUG_ARG
) {
480 /* Let's not give too much away in the log file */
482 #ifdef DEBUG_PASSWORD
483 _pam_log(LOG_INFO
, "Verify user `%s' with password `%s'",
486 _pam_log(LOG_INFO
, "Verify user `%s'", username
);
490 /* Retrieve membership-string here */
491 for ( i
=0; i
<argc
; i
++ ) {
493 if ((strncmp(argv
[i
], "require_membership_of", strlen("require_membership_of")) == 0)
494 || (strncmp(argv
[i
], "require-membership-of", strlen("require-membership-of")) == 0)) {
497 char *parm
= strdup(argv
[i
]);
499 if ( (p
= strchr( parm
, '=' )) == NULL
) {
500 _pam_log(LOG_INFO
, "no \"=\" delimiter for \"require_membership_of\" found\n");
504 member
= strdup(p
+1);
508 /* Now use the username to look up password */
509 return winbind_auth_request(username
, password
, member
, ctrl
);
513 int pam_sm_setcred(pam_handle_t
*pamh
, int flags
,
514 int argc
, const char **argv
)
520 * Account management. We want to verify that the account exists
521 * before returning PAM_SUCCESS
524 int pam_sm_acct_mgmt(pam_handle_t
*pamh
, int flags
,
525 int argc
, const char **argv
)
527 const char *username
;
528 int retval
= PAM_USER_UNKNOWN
;
530 /* parse arguments */
531 int ctrl
= _pam_parse(argc
, argv
);
533 /* Get the username */
534 retval
= pam_get_user(pamh
, &username
, NULL
);
535 if ((retval
!= PAM_SUCCESS
) || (!username
)) {
536 if (ctrl
& WINBIND_DEBUG_ARG
)
537 _pam_log(LOG_DEBUG
,"can not get the username");
538 return PAM_SERVICE_ERR
;
541 /* Verify the username */
542 retval
= valid_user(username
);
545 /* some sort of system error. The log was already printed */
546 return PAM_SERVICE_ERR
;
548 /* the user does not exist */
549 if (ctrl
& WINBIND_DEBUG_ARG
)
550 _pam_log(LOG_NOTICE
, "user `%s' not found",
552 if (ctrl
& WINBIND_UNKNOWN_OK_ARG
)
554 return PAM_USER_UNKNOWN
;
556 /* Otherwise, the authentication looked good */
557 _pam_log(LOG_NOTICE
, "user '%s' granted access", username
);
560 /* we don't know anything about this return value */
561 _pam_log(LOG_ERR
, "internal module error (retval = %d, user = `%s'",
563 return PAM_SERVICE_ERR
;
566 /* should not be reached */
570 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
,
571 int argc
, const char **argv
)
573 /* parse arguments */
574 int ctrl
= _pam_parse(argc
, argv
);
575 if (ctrl
& WINBIND_DEBUG_ARG
)
576 _pam_log(LOG_DEBUG
,"libpam_winbind:pam_sm_open_session handler");
580 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
,
581 int argc
, const char **argv
)
583 /* parse arguments */
584 int ctrl
= _pam_parse(argc
, argv
);
585 if (ctrl
& WINBIND_DEBUG_ARG
)
586 _pam_log(LOG_DEBUG
,"libpam_winbind:pam_sm_close_session handler");
592 PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t
* pamh
, int flags
,
593 int argc
, const char **argv
)
597 unsigned int ctrl
= _pam_parse(argc
, argv
);
599 /* <DO NOT free() THESE> */
601 const char *member
= NULL
;
602 char *pass_old
, *pass_new
;
603 /* </DO NOT free() THESE> */
610 * First get the name of a user
612 retval
= pam_get_user(pamh
, &user
, "Username: ");
613 if (retval
== PAM_SUCCESS
) {
615 _pam_log(LOG_ERR
, "username was NULL!");
616 return PAM_USER_UNKNOWN
;
618 if (retval
== PAM_SUCCESS
&& on(WINBIND_DEBUG_ARG
, ctrl
))
619 _pam_log(LOG_DEBUG
, "username [%s] obtained",
622 if (on(WINBIND_DEBUG_ARG
, ctrl
))
624 "password - could not identify user");
629 * obtain and verify the current password (OLDAUTHTOK) for
633 if (flags
& PAM_PRELIM_CHECK
) {
635 /* instruct user what is happening */
636 #define greeting "Changing password for "
637 Announce
= (char *) malloc(sizeof(greeting
) + strlen(user
));
638 if (Announce
== NULL
) {
640 "password - out of memory");
643 (void) strcpy(Announce
, greeting
);
644 (void) strcpy(Announce
+ sizeof(greeting
) - 1, user
);
647 lctrl
= ctrl
| WINBIND__OLD_PASSWORD
;
648 retval
= _winbind_read_password(pamh
, lctrl
650 ,"(current) NT password: "
652 ,(const char **) &pass_old
);
655 if (retval
!= PAM_SUCCESS
) {
657 ,"password - (old) token not obtained");
660 /* verify that this is the password for this user */
662 retval
= winbind_auth_request(user
, pass_old
, member
, ctrl
);
664 if (retval
!= PAM_ACCT_EXPIRED
665 && retval
!= PAM_AUTHTOK_EXPIRED
666 && retval
!= PAM_NEW_AUTHTOK_REQD
667 && retval
!= PAM_SUCCESS
) {
672 retval
= pam_set_item(pamh
, PAM_OLDAUTHTOK
, (const void *) pass_old
);
674 if (retval
!= PAM_SUCCESS
) {
676 "failed to set PAM_OLDAUTHTOK");
678 } else if (flags
& PAM_UPDATE_AUTHTOK
) {
681 * obtain the proposed password
685 * get the old token back.
688 retval
= pam_get_item(pamh
, PAM_OLDAUTHTOK
689 ,(const void **) &pass_old
);
691 if (retval
!= PAM_SUCCESS
) {
692 _pam_log(LOG_NOTICE
, "user not authenticated");
698 if (on(WINBIND_USE_AUTHTOK_ARG
, lctrl
)) {
699 ctrl
= WINBIND_USE_FIRST_PASS_ARG
| lctrl
;
702 retval
= PAM_AUTHTOK_ERR
;
703 while ((retval
!= PAM_SUCCESS
) && (retry
++ < MAX_PASSWD_TRIES
)) {
705 * use_authtok is to force the use of a previously entered
706 * password -- needed for pluggable password strength checking
709 retval
= _winbind_read_password(pamh
, lctrl
711 ,"Enter new NT password: "
712 ,"Retype new NT password: "
713 ,(const char **) &pass_new
);
715 if (retval
!= PAM_SUCCESS
) {
716 if (on(WINBIND_DEBUG_ARG
, ctrl
)) {
718 ,"password - new password not obtained");
720 pass_old
= NULL
;/* tidy up */
725 * At this point we know who the user is and what they
726 * propose as their new password. Verify that the new
727 * password is acceptable.
730 if (pass_new
[0] == '\0') {/* "\0" password = NULL */
736 * By reaching here we have approved the passwords and must now
737 * rebuild the password database file.
740 retval
= winbind_chauthtok_request(user
, pass_old
, pass_new
, ctrl
);
741 _pam_overwrite(pass_new
);
742 _pam_overwrite(pass_old
);
743 pass_old
= pass_new
= NULL
;
745 retval
= PAM_SERVICE_ERR
;
753 /* static module data */
755 struct pam_module _pam_winbind_modstruct
= {
761 pam_sm_close_session
,
768 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
769 * Copyright (c) Tim Potter <tpot@samba.org> 2000
770 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
771 * Copyright (c) Jan Rêkorajski 1999.
772 * Copyright (c) Andrew G. Morgan 1996-8.
773 * Copyright (c) Alex O. Yuriev, 1996.
774 * Copyright (c) Cristian Gafton 1996.
775 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
777 * Redistribution and use in source and binary forms, with or without
778 * modification, are permitted provided that the following conditions
780 * 1. Redistributions of source code must retain the above copyright
781 * notice, and the entire permission notice in its entirety,
782 * including the disclaimer of warranties.
783 * 2. Redistributions in binary form must reproduce the above copyright
784 * notice, this list of conditions and the following disclaimer in the
785 * documentation and/or other materials provided with the distribution.
786 * 3. The name of the author may not be used to endorse or promote
787 * products derived from this software without specific prior
788 * written permission.
790 * ALTERNATIVELY, this product may be distributed under the terms of
791 * the GNU Public License, in which case the provisions of the GPL are
792 * required INSTEAD OF the above restrictions. (This clause is
793 * necessary due to a potential bad interaction between the GPL and
794 * the restrictions contained in a BSD-style copyright.)
796 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
797 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
798 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
799 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
800 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
801 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
802 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
803 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
804 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
805 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
806 * OF THE POSSIBILITY OF SUCH DAMAGE.