WHATSNEW: Update changes since 3.3.0pre2.
[Samba.git] / source / nsswitch / pam_winbind.c
blob55931146266d6f59e89d08f0127edc7392e1fc6e
1 /* pam_winbind module
3 Copyright Andrew Tridgell <tridge@samba.org> 2000
4 Copyright Tim Potter <tpot@samba.org> 2000
5 Copyright Andrew Bartlett <abartlet@samba.org> 2002
6 Copyright Guenther Deschner <gd@samba.org> 2005-2008
8 largely based on pam_userdb by Cristian Gafton <gafton@redhat.com> also
9 contains large slabs of code from pam_unix by Elliot Lee
10 <sopwith@redhat.com> (see copyright below for full details)
13 #include "pam_winbind.h"
15 static const char *_pam_error_code_str(int err)
17 switch (err) {
18 case PAM_SUCCESS:
19 return "PAM_SUCCESS";
20 case PAM_OPEN_ERR:
21 return "PAM_OPEN_ERR";
22 case PAM_SYMBOL_ERR:
23 return "PAM_SYMBOL_ERR";
24 case PAM_SERVICE_ERR:
25 return "PAM_SERVICE_ERR";
26 case PAM_SYSTEM_ERR:
27 return "PAM_SYSTEM_ERR";
28 case PAM_BUF_ERR:
29 return "PAM_BUF_ERR";
30 case PAM_PERM_DENIED:
31 return "PAM_PERM_DENIED";
32 case PAM_AUTH_ERR:
33 return "PAM_AUTH_ERR";
34 case PAM_CRED_INSUFFICIENT:
35 return "PAM_CRED_INSUFFICIENT";
36 case PAM_AUTHINFO_UNAVAIL:
37 return "PAM_AUTHINFO_UNAVAIL";
38 case PAM_USER_UNKNOWN:
39 return "PAM_USER_UNKNOWN";
40 case PAM_MAXTRIES:
41 return "PAM_MAXTRIES";
42 case PAM_NEW_AUTHTOK_REQD:
43 return "PAM_NEW_AUTHTOK_REQD";
44 case PAM_ACCT_EXPIRED:
45 return "PAM_ACCT_EXPIRED";
46 case PAM_SESSION_ERR:
47 return "PAM_SESSION_ERR";
48 case PAM_CRED_UNAVAIL:
49 return "PAM_CRED_UNAVAIL";
50 case PAM_CRED_EXPIRED:
51 return "PAM_CRED_EXPIRED";
52 case PAM_CRED_ERR:
53 return "PAM_CRED_ERR";
54 case PAM_NO_MODULE_DATA:
55 return "PAM_NO_MODULE_DATA";
56 case PAM_CONV_ERR:
57 return "PAM_CONV_ERR";
58 case PAM_AUTHTOK_ERR:
59 return "PAM_AUTHTOK_ERR";
60 case PAM_AUTHTOK_RECOVERY_ERR:
61 return "PAM_AUTHTOK_RECOVERY_ERR";
62 case PAM_AUTHTOK_LOCK_BUSY:
63 return "PAM_AUTHTOK_LOCK_BUSY";
64 case PAM_AUTHTOK_DISABLE_AGING:
65 return "PAM_AUTHTOK_DISABLE_AGING";
66 case PAM_TRY_AGAIN:
67 return "PAM_TRY_AGAIN";
68 case PAM_IGNORE:
69 return "PAM_IGNORE";
70 case PAM_ABORT:
71 return "PAM_ABORT";
72 case PAM_AUTHTOK_EXPIRED:
73 return "PAM_AUTHTOK_EXPIRED";
74 #ifdef PAM_MODULE_UNKNOWN
75 case PAM_MODULE_UNKNOWN:
76 return "PAM_MODULE_UNKNOWN";
77 #endif
78 #ifdef PAM_BAD_ITEM
79 case PAM_BAD_ITEM:
80 return "PAM_BAD_ITEM";
81 #endif
82 #ifdef PAM_CONV_AGAIN
83 case PAM_CONV_AGAIN:
84 return "PAM_CONV_AGAIN";
85 #endif
86 #ifdef PAM_INCOMPLETE
87 case PAM_INCOMPLETE:
88 return "PAM_INCOMPLETE";
89 #endif
90 default:
91 return NULL;
95 #define _PAM_LOG_FUNCTION_ENTER(function, ctx) \
96 do { \
97 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] ENTER: " \
98 function " (flags: 0x%04x)", ctx->pamh, ctx->flags); \
99 _pam_log_state(ctx); \
100 } while (0)
102 #define _PAM_LOG_FUNCTION_LEAVE(function, ctx, retval) \
103 do { \
104 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] LEAVE: " \
105 function " returning %d (%s)", ctx->pamh, retval, \
106 _pam_error_code_str(retval)); \
107 _pam_log_state(ctx); \
108 } while (0)
110 /* data tokens */
112 #define MAX_PASSWD_TRIES 3
115 * Work around the pam API that has functions with void ** as parameters
116 * These lead to strict aliasing warnings with gcc.
118 static int _pam_get_item(const pam_handle_t *pamh,
119 int item_type,
120 const void *_item)
122 const void **item = (const void **)_item;
123 return pam_get_item(pamh, item_type, item);
125 static int _pam_get_data(const pam_handle_t *pamh,
126 const char *module_data_name,
127 const void *_data)
129 const void **data = (const void **)_data;
130 return pam_get_data(pamh, module_data_name, data);
133 /* some syslogging */
135 #ifdef HAVE_PAM_VSYSLOG
136 static void _pam_log_int(const pam_handle_t *pamh,
137 int err,
138 const char *format,
139 va_list args)
141 pam_vsyslog(pamh, err, format, args);
143 #else
144 static void _pam_log_int(const pam_handle_t *pamh,
145 int err,
146 const char *format,
147 va_list args)
149 char *format2 = NULL;
150 const char *service;
152 _pam_get_item(pamh, PAM_SERVICE, &service);
154 format2 = (char *)malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
155 if (format2 == NULL) {
156 /* what else todo ? */
157 vsyslog(err, format, args);
158 return;
161 sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
162 vsyslog(err, format2, args);
163 SAFE_FREE(format2);
165 #endif /* HAVE_PAM_VSYSLOG */
167 static bool _pam_log_is_silent(int ctrl)
169 return on(ctrl, WINBIND_SILENT);
172 static void _pam_log(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
173 static void _pam_log(struct pwb_context *r, int err, const char *format, ...)
175 va_list args;
177 if (_pam_log_is_silent(r->ctrl)) {
178 return;
181 va_start(args, format);
182 _pam_log_int(r->pamh, err, format, args);
183 va_end(args);
185 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
186 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
188 va_list args;
190 if (_pam_log_is_silent(ctrl)) {
191 return;
194 va_start(args, format);
195 _pam_log_int(pamh, err, format, args);
196 va_end(args);
199 static bool _pam_log_is_debug_enabled(int ctrl)
201 if (ctrl == -1) {
202 return false;
205 if (_pam_log_is_silent(ctrl)) {
206 return false;
209 if (!(ctrl & WINBIND_DEBUG_ARG)) {
210 return false;
213 return true;
216 static bool _pam_log_is_debug_state_enabled(int ctrl)
218 if (!(ctrl & WINBIND_DEBUG_STATE)) {
219 return false;
222 return _pam_log_is_debug_enabled(ctrl);
225 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
226 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...)
228 va_list args;
230 if (!_pam_log_is_debug_enabled(r->ctrl)) {
231 return;
234 va_start(args, format);
235 _pam_log_int(r->pamh, err, format, args);
236 va_end(args);
238 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
239 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
241 va_list args;
243 if (!_pam_log_is_debug_enabled(ctrl)) {
244 return;
247 va_start(args, format);
248 _pam_log_int(pamh, err, format, args);
249 va_end(args);
252 static void _pam_log_state_datum(struct pwb_context *ctx,
253 int item_type,
254 const char *key,
255 int is_string)
257 const void *data = NULL;
258 if (item_type != 0) {
259 pam_get_item(ctx->pamh, item_type, &data);
260 } else {
261 pam_get_data(ctx->pamh, key, &data);
263 if (data != NULL) {
264 const char *type = (item_type != 0) ? "ITEM" : "DATA";
265 if (is_string != 0) {
266 _pam_log_debug(ctx, LOG_DEBUG,
267 "[pamh: %p] STATE: %s(%s) = \"%s\" (%p)",
268 ctx->pamh, type, key, (const char *)data,
269 data);
270 } else {
271 _pam_log_debug(ctx, LOG_DEBUG,
272 "[pamh: %p] STATE: %s(%s) = %p",
273 ctx->pamh, type, key, data);
278 #define _PAM_LOG_STATE_DATA_POINTER(ctx, module_data_name) \
279 _pam_log_state_datum(ctx, 0, module_data_name, 0)
281 #define _PAM_LOG_STATE_DATA_STRING(ctx, module_data_name) \
282 _pam_log_state_datum(ctx, 0, module_data_name, 1)
284 #define _PAM_LOG_STATE_ITEM_POINTER(ctx, item_type) \
285 _pam_log_state_datum(ctx, item_type, #item_type, 0)
287 #define _PAM_LOG_STATE_ITEM_STRING(ctx, item_type) \
288 _pam_log_state_datum(ctx, item_type, #item_type, 1)
290 #ifdef DEBUG_PASSWORD
291 #define _LOG_PASSWORD_AS_STRING 1
292 #else
293 #define _LOG_PASSWORD_AS_STRING 0
294 #endif
296 #define _PAM_LOG_STATE_ITEM_PASSWORD(ctx, item_type) \
297 _pam_log_state_datum(ctx, item_type, #item_type, \
298 _LOG_PASSWORD_AS_STRING)
300 static void _pam_log_state(struct pwb_context *ctx)
302 if (!_pam_log_is_debug_state_enabled(ctx->ctrl)) {
303 return;
306 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_SERVICE);
307 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER);
308 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_TTY);
309 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RHOST);
310 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RUSER);
311 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_OLDAUTHTOK);
312 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_AUTHTOK);
313 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER_PROMPT);
314 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_CONV);
315 #ifdef PAM_FAIL_DELAY
316 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_FAIL_DELAY);
317 #endif
318 #ifdef PAM_REPOSITORY
319 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_REPOSITORY);
320 #endif
322 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_HOMEDIR);
323 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSCRIPT);
324 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSERVER);
325 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_PROFILEPATH);
326 _PAM_LOG_STATE_DATA_STRING(ctx,
327 PAM_WINBIND_NEW_AUTHTOK_REQD);
328 /* Use atoi to get PAM result code */
329 _PAM_LOG_STATE_DATA_STRING(ctx,
330 PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH);
331 _PAM_LOG_STATE_DATA_POINTER(ctx, PAM_WINBIND_PWD_LAST_SET);
334 static int _pam_parse(const pam_handle_t *pamh,
335 int flags,
336 int argc,
337 const char **argv,
338 dictionary **result_d)
340 int ctrl = 0;
341 const char *config_file = NULL;
342 int i;
343 const char **v;
344 dictionary *d = NULL;
346 if (flags & PAM_SILENT) {
347 ctrl |= WINBIND_SILENT;
350 for (i=argc,v=argv; i-- > 0; ++v) {
351 if (!strncasecmp(*v, "config", strlen("config"))) {
352 ctrl |= WINBIND_CONFIG_FILE;
353 config_file = v[i];
354 break;
358 if (config_file == NULL) {
359 config_file = PAM_WINBIND_CONFIG_FILE;
362 d = iniparser_load(config_file);
363 if (d == NULL) {
364 goto config_from_pam;
367 if (iniparser_getboolean(d, "global:debug", false)) {
368 ctrl |= WINBIND_DEBUG_ARG;
371 if (iniparser_getboolean(d, "global:debug_state", false)) {
372 ctrl |= WINBIND_DEBUG_STATE;
375 if (iniparser_getboolean(d, "global:cached_login", false)) {
376 ctrl |= WINBIND_CACHED_LOGIN;
379 if (iniparser_getboolean(d, "global:krb5_auth", false)) {
380 ctrl |= WINBIND_KRB5_AUTH;
383 if (iniparser_getboolean(d, "global:silent", false)) {
384 ctrl |= WINBIND_SILENT;
387 if (iniparser_getstr(d, "global:krb5_ccache_type") != NULL) {
388 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
391 if ((iniparser_getstr(d, "global:require-membership-of") != NULL) ||
392 (iniparser_getstr(d, "global:require_membership_of") != NULL)) {
393 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
396 if (iniparser_getboolean(d, "global:try_first_pass", false)) {
397 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
400 if (iniparser_getint(d, "global:warn_pwd_expire", 0)) {
401 ctrl |= WINBIND_WARN_PWD_EXPIRE;
404 config_from_pam:
405 /* step through arguments */
406 for (i=argc,v=argv; i-- > 0; ++v) {
408 /* generic options */
409 if (!strcmp(*v,"debug"))
410 ctrl |= WINBIND_DEBUG_ARG;
411 else if (!strcasecmp(*v, "debug_state"))
412 ctrl |= WINBIND_DEBUG_STATE;
413 else if (!strcasecmp(*v, "silent"))
414 ctrl |= WINBIND_SILENT;
415 else if (!strcasecmp(*v, "use_authtok"))
416 ctrl |= WINBIND_USE_AUTHTOK_ARG;
417 else if (!strcasecmp(*v, "use_first_pass"))
418 ctrl |= WINBIND_USE_FIRST_PASS_ARG;
419 else if (!strcasecmp(*v, "try_first_pass"))
420 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
421 else if (!strcasecmp(*v, "unknown_ok"))
422 ctrl |= WINBIND_UNKNOWN_OK_ARG;
423 else if (!strncasecmp(*v, "require_membership_of",
424 strlen("require_membership_of")))
425 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
426 else if (!strncasecmp(*v, "require-membership-of",
427 strlen("require-membership-of")))
428 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
429 else if (!strcasecmp(*v, "krb5_auth"))
430 ctrl |= WINBIND_KRB5_AUTH;
431 else if (!strncasecmp(*v, "krb5_ccache_type",
432 strlen("krb5_ccache_type")))
433 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
434 else if (!strcasecmp(*v, "cached_login"))
435 ctrl |= WINBIND_CACHED_LOGIN;
436 else {
437 __pam_log(pamh, ctrl, LOG_ERR,
438 "pam_parse: unknown option: %s", *v);
439 return -1;
444 if (result_d) {
445 *result_d = d;
446 } else {
447 if (d) {
448 iniparser_freedict(d);
452 return ctrl;
455 static void _pam_winbind_free_context(struct pwb_context *ctx)
457 if (ctx->dict) {
458 iniparser_freedict(ctx->dict);
461 SAFE_FREE(ctx);
464 static int _pam_winbind_init_context(pam_handle_t *pamh,
465 int flags,
466 int argc,
467 const char **argv,
468 struct pwb_context **ctx_p)
470 struct pwb_context *r = NULL;
472 r = (struct pwb_context *)malloc(sizeof(struct pwb_context));
473 if (!r) {
474 return PAM_BUF_ERR;
477 ZERO_STRUCTP(r);
479 r->pamh = pamh;
480 r->flags = flags;
481 r->argc = argc;
482 r->argv = argv;
483 r->ctrl = _pam_parse(pamh, flags, argc, argv, &r->dict);
484 if (r->ctrl == -1) {
485 _pam_winbind_free_context(r);
486 return PAM_SYSTEM_ERR;
489 *ctx_p = r;
491 return PAM_SUCCESS;
494 static void _pam_winbind_cleanup_func(pam_handle_t *pamh,
495 void *data,
496 int error_status)
498 int ctrl = _pam_parse(pamh, 0, 0, NULL, NULL);
499 if (_pam_log_is_debug_state_enabled(ctrl)) {
500 __pam_log_debug(pamh, ctrl, LOG_DEBUG,
501 "[pamh: %p] CLEAN: cleaning up PAM data %p "
502 "(error_status = %d)", pamh, data,
503 error_status);
505 SAFE_FREE(data);
509 static const struct ntstatus_errors {
510 const char *ntstatus_string;
511 const char *error_string;
512 } ntstatus_errors[] = {
513 {"NT_STATUS_OK",
514 "Success"},
515 {"NT_STATUS_BACKUP_CONTROLLER",
516 "No primary Domain Controler available"},
517 {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND",
518 "No domain controllers found"},
519 {"NT_STATUS_NO_LOGON_SERVERS",
520 "No logon servers"},
521 {"NT_STATUS_PWD_TOO_SHORT",
522 "Password too short"},
523 {"NT_STATUS_PWD_TOO_RECENT",
524 "The password of this user is too recent to change"},
525 {"NT_STATUS_PWD_HISTORY_CONFLICT",
526 "Password is already in password history"},
527 {"NT_STATUS_PASSWORD_EXPIRED",
528 "Your password has expired"},
529 {"NT_STATUS_PASSWORD_MUST_CHANGE",
530 "You need to change your password now"},
531 {"NT_STATUS_INVALID_WORKSTATION",
532 "You are not allowed to logon from this workstation"},
533 {"NT_STATUS_INVALID_LOGON_HOURS",
534 "You are not allowed to logon at this time"},
535 {"NT_STATUS_ACCOUNT_EXPIRED",
536 "Your account has expired. "
537 "Please contact your System administrator"}, /* SCNR */
538 {"NT_STATUS_ACCOUNT_DISABLED",
539 "Your account is disabled. "
540 "Please contact your System administrator"}, /* SCNR */
541 {"NT_STATUS_ACCOUNT_LOCKED_OUT",
542 "Your account has been locked. "
543 "Please contact your System administrator"}, /* SCNR */
544 {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
545 "Invalid Trust Account"},
546 {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT",
547 "Invalid Trust Account"},
548 {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT",
549 "Invalid Trust Account"},
550 {"NT_STATUS_ACCESS_DENIED",
551 "Access is denied"},
552 {NULL, NULL}
555 static const char *_get_ntstatus_error_string(const char *nt_status_string)
557 int i;
558 for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) {
559 if (!strcasecmp(ntstatus_errors[i].ntstatus_string,
560 nt_status_string)) {
561 return ntstatus_errors[i].error_string;
564 return NULL;
567 /* --- authentication management functions --- */
569 /* Attempt a conversation */
571 static int converse(const pam_handle_t *pamh,
572 int nargs,
573 struct pam_message **message,
574 struct pam_response **response)
576 int retval;
577 struct pam_conv *conv;
579 retval = _pam_get_item(pamh, PAM_CONV, &conv);
580 if (retval == PAM_SUCCESS) {
581 retval = conv->conv(nargs,
582 (const struct pam_message **)message,
583 response, conv->appdata_ptr);
586 return retval; /* propagate error status */
590 static int _make_remark(struct pwb_context *ctx,
591 int type,
592 const char *text)
594 int retval = PAM_SUCCESS;
596 struct pam_message *pmsg[1], msg[1];
597 struct pam_response *resp;
599 if (ctx->flags & WINBIND_SILENT) {
600 return PAM_SUCCESS;
603 pmsg[0] = &msg[0];
604 msg[0].msg = discard_const_p(char, text);
605 msg[0].msg_style = type;
607 resp = NULL;
608 retval = converse(ctx->pamh, 1, pmsg, &resp);
610 if (resp) {
611 _pam_drop_reply(resp, 1);
613 return retval;
616 static int _make_remark_v(struct pwb_context *ctx,
617 int type,
618 const char *format,
619 va_list args)
621 char *var;
622 int ret;
624 ret = vasprintf(&var, format, args);
625 if (ret < 0) {
626 _pam_log(ctx, LOG_ERR, "memory allocation failure");
627 return ret;
630 ret = _make_remark(ctx, type, var);
631 SAFE_FREE(var);
632 return ret;
635 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
636 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...)
638 int ret;
639 va_list args;
641 va_start(args, format);
642 ret = _make_remark_v(ctx, type, format, args);
643 va_end(args);
644 return ret;
647 static int pam_winbind_request(struct pwb_context *ctx,
648 enum winbindd_cmd req_type,
649 struct winbindd_request *request,
650 struct winbindd_response *response)
652 /* Fill in request and send down pipe */
653 winbindd_init_request(request, req_type);
655 if (winbind_write_sock(request, sizeof(*request), 0, 0) == -1) {
656 _pam_log(ctx, LOG_ERR,
657 "pam_winbind_request: write to socket failed!");
658 winbind_close_sock();
659 return PAM_SERVICE_ERR;
662 /* Wait for reply */
663 if (winbindd_read_reply(response) == -1) {
664 _pam_log(ctx, LOG_ERR,
665 "pam_winbind_request: read from socket failed!");
666 winbind_close_sock();
667 return PAM_SERVICE_ERR;
670 /* We are done with the socket - close it and avoid mischeif */
671 winbind_close_sock();
673 /* Copy reply data from socket */
674 if (response->result == WINBINDD_OK) {
675 return PAM_SUCCESS;
678 /* no need to check for pam_error codes for getpwnam() */
679 switch (req_type) {
681 case WINBINDD_GETPWNAM:
682 case WINBINDD_LOOKUPNAME:
683 if (strlen(response->data.auth.nt_status_string) > 0) {
684 _pam_log(ctx, LOG_ERR,
685 "request failed, NT error was %s",
686 response->data.auth.nt_status_string);
687 } else {
688 _pam_log(ctx, LOG_ERR, "request failed");
690 return PAM_USER_UNKNOWN;
691 default:
692 break;
695 if (response->data.auth.pam_error != PAM_SUCCESS) {
696 _pam_log(ctx, LOG_ERR,
697 "request failed: %s, "
698 "PAM error was %s (%d), NT error was %s",
699 response->data.auth.error_string,
700 pam_strerror(ctx->pamh, response->data.auth.pam_error),
701 response->data.auth.pam_error,
702 response->data.auth.nt_status_string);
703 return response->data.auth.pam_error;
706 _pam_log(ctx, LOG_ERR, "request failed, but PAM error 0!");
708 return PAM_SERVICE_ERR;
711 static int pam_winbind_request_log(struct pwb_context *ctx,
712 enum winbindd_cmd req_type,
713 struct winbindd_request *request,
714 struct winbindd_response *response,
715 const char *user)
717 int retval;
719 retval = pam_winbind_request(ctx, req_type, request, response);
721 switch (retval) {
722 case PAM_AUTH_ERR:
723 /* incorrect password */
724 _pam_log(ctx, LOG_WARNING, "user '%s' denied access "
725 "(incorrect password or invalid membership)", user);
726 return retval;
727 case PAM_ACCT_EXPIRED:
728 /* account expired */
729 _pam_log(ctx, LOG_WARNING, "user '%s' account expired",
730 user);
731 return retval;
732 case PAM_AUTHTOK_EXPIRED:
733 /* password expired */
734 _pam_log(ctx, LOG_WARNING, "user '%s' password expired",
735 user);
736 return retval;
737 case PAM_NEW_AUTHTOK_REQD:
738 /* new password required */
739 _pam_log(ctx, LOG_WARNING, "user '%s' new password "
740 "required", user);
741 return retval;
742 case PAM_USER_UNKNOWN:
743 /* the user does not exist */
744 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
745 user);
746 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
747 return PAM_IGNORE;
749 return retval;
750 case PAM_SUCCESS:
751 /* Otherwise, the authentication looked good */
752 switch (req_type) {
753 case WINBINDD_INFO:
754 break;
755 case WINBINDD_PAM_AUTH:
756 _pam_log(ctx, LOG_NOTICE,
757 "user '%s' granted access", user);
758 break;
759 case WINBINDD_PAM_CHAUTHTOK:
760 _pam_log(ctx, LOG_NOTICE,
761 "user '%s' password changed", user);
762 break;
763 default:
764 _pam_log(ctx, LOG_NOTICE,
765 "user '%s' OK", user);
766 break;
769 return retval;
770 default:
771 /* we don't know anything about this return value */
772 _pam_log(ctx, LOG_ERR,
773 "internal module error (retval = %d, user = '%s')",
774 retval, user);
775 return retval;
780 * send a password expiry message if required
782 * @param ctx PAM winbind context.
783 * @param next_change expected (calculated) next expiry date.
784 * @param already_expired pointer to a boolean to indicate if the password is
785 * already expired.
787 * @return boolean Returns true if message has been sent, false if not.
790 static bool _pam_send_password_expiry_message(struct pwb_context *ctx,
791 time_t next_change,
792 time_t now,
793 int warn_pwd_expire,
794 bool *already_expired)
796 int days = 0;
797 struct tm tm_now, tm_next_change;
799 if (already_expired) {
800 *already_expired = false;
803 if (next_change <= now) {
804 PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PASSWORD_EXPIRED");
805 if (already_expired) {
806 *already_expired = true;
808 return true;
811 if ((next_change < 0) ||
812 (next_change > now + warn_pwd_expire * SECONDS_PER_DAY)) {
813 return false;
816 if ((localtime_r(&now, &tm_now) == NULL) ||
817 (localtime_r(&next_change, &tm_next_change) == NULL)) {
818 return false;
821 days = (tm_next_change.tm_yday+tm_next_change.tm_year*365) -
822 (tm_now.tm_yday+tm_now.tm_year*365);
824 if (days == 0) {
825 _make_remark(ctx, PAM_TEXT_INFO,
826 "Your password expires today");
827 return true;
830 if (days > 0 && days < warn_pwd_expire) {
831 _make_remark_format(ctx, PAM_TEXT_INFO,
832 "Your password will expire in %d %s",
833 days, (days > 1) ? "days":"day");
834 return true;
837 return false;
841 * Send a warning if the password expires in the near future
843 * @param ctx PAM winbind context.
844 * @param response The full authentication response structure.
845 * @param already_expired boolean, is the pwd already expired?
847 * @return void.
850 static void _pam_warn_password_expiry(struct pwb_context *ctx,
851 const struct winbindd_response *response,
852 int warn_pwd_expire,
853 bool *already_expired)
855 time_t now = time(NULL);
856 time_t next_change = 0;
858 if (already_expired) {
859 *already_expired = false;
862 /* accounts with ACB_PWNOEXP set never receive a warning */
863 if (response->data.auth.info3.acct_flags & ACB_PWNOEXP) {
864 return;
867 /* no point in sending a warning if this is a grace logon */
868 if (PAM_WB_GRACE_LOGON(response->data.auth.info3.user_flgs)) {
869 return;
872 /* check if the info3 must change timestamp has been set */
873 next_change = response->data.auth.info3.pass_must_change_time;
875 if (_pam_send_password_expiry_message(ctx, next_change, now,
876 warn_pwd_expire,
877 already_expired)) {
878 return;
881 /* now check for the global password policy */
882 /* good catch from Ralf Haferkamp: an expiry of "never" is translated
883 * to -1 */
884 if (response->data.auth.policy.expire <= 0) {
885 return;
888 next_change = response->data.auth.info3.pass_last_set_time +
889 response->data.auth.policy.expire;
891 if (_pam_send_password_expiry_message(ctx, next_change, now,
892 warn_pwd_expire,
893 already_expired)) {
894 return;
897 /* no warning sent */
900 #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0)
903 * Append a string, making sure not to overflow and to always return a
904 * NULL-terminated string.
906 * @param dest Destination string buffer (must already be NULL-terminated).
907 * @param src Source string buffer.
908 * @param dest_buffer_size Size of dest buffer in bytes.
910 * @return false if dest buffer is not big enough (no bytes copied), true on
911 * success.
914 static bool safe_append_string(char *dest,
915 const char *src,
916 int dest_buffer_size)
918 int dest_length = strlen(dest);
919 int src_length = strlen(src);
921 if (dest_length + src_length + 1 > dest_buffer_size) {
922 return false;
925 memcpy(dest + dest_length, src, src_length + 1);
926 return true;
930 * Convert a names into a SID string, appending it to a buffer.
932 * @param ctx PAM winbind context.
933 * @param user User in PAM request.
934 * @param name Name to convert.
935 * @param sid_list_buffer Where to append the string sid.
936 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
938 * @return false on failure, true on success.
940 static bool winbind_name_to_sid_string(struct pwb_context *ctx,
941 const char *user,
942 const char *name,
943 char *sid_list_buffer,
944 int sid_list_buffer_size)
946 const char* sid_string;
947 struct winbindd_response sid_response;
949 /* lookup name? */
950 if (IS_SID_STRING(name)) {
951 sid_string = name;
952 } else {
953 struct winbindd_request sid_request;
955 ZERO_STRUCT(sid_request);
956 ZERO_STRUCT(sid_response);
958 _pam_log_debug(ctx, LOG_DEBUG,
959 "no sid given, looking up: %s\n", name);
961 /* fortunatly winbindd can handle non-separated names */
962 strncpy(sid_request.data.name.name, name,
963 sizeof(sid_request.data.name.name) - 1);
965 if (pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
966 &sid_request, &sid_response,
967 user)) {
968 _pam_log(ctx, LOG_INFO,
969 "could not lookup name: %s\n", name);
970 return false;
973 sid_string = sid_response.data.sid.sid;
976 if (!safe_append_string(sid_list_buffer, sid_string,
977 sid_list_buffer_size)) {
978 return false;
981 return true;
985 * Convert a list of names into a list of sids.
987 * @param ctx PAM winbind context.
988 * @param user User in PAM request.
989 * @param name_list List of names or string sids, separated by commas.
990 * @param sid_list_buffer Where to put the list of string sids.
991 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
993 * @return false on failure, true on success.
995 static bool winbind_name_list_to_sid_string_list(struct pwb_context *ctx,
996 const char *user,
997 const char *name_list,
998 char *sid_list_buffer,
999 int sid_list_buffer_size)
1001 bool result = false;
1002 char *current_name = NULL;
1003 const char *search_location;
1004 const char *comma;
1006 if (sid_list_buffer_size > 0) {
1007 sid_list_buffer[0] = 0;
1010 search_location = name_list;
1011 while ((comma = strstr(search_location, ",")) != NULL) {
1012 current_name = strndup(search_location,
1013 comma - search_location);
1014 if (NULL == current_name) {
1015 goto out;
1018 if (!winbind_name_to_sid_string(ctx, user,
1019 current_name,
1020 sid_list_buffer,
1021 sid_list_buffer_size)) {
1022 goto out;
1025 SAFE_FREE(current_name);
1027 if (!safe_append_string(sid_list_buffer, ",",
1028 sid_list_buffer_size)) {
1029 goto out;
1032 search_location = comma + 1;
1035 if (!winbind_name_to_sid_string(ctx, user, search_location,
1036 sid_list_buffer,
1037 sid_list_buffer_size)) {
1038 goto out;
1041 result = true;
1043 out:
1044 SAFE_FREE(current_name);
1045 return result;
1049 * put krb5ccname variable into environment
1051 * @param ctx PAM winbind context.
1052 * @param krb5ccname env variable retrieved from winbindd.
1054 * @return void.
1057 static void _pam_setup_krb5_env(struct pwb_context *ctx,
1058 const char *krb5ccname)
1060 char var[PATH_MAX];
1061 int ret;
1063 if (off(ctx->ctrl, WINBIND_KRB5_AUTH)) {
1064 return;
1067 if (!krb5ccname || (strlen(krb5ccname) == 0)) {
1068 return;
1071 _pam_log_debug(ctx, LOG_DEBUG,
1072 "request returned KRB5CCNAME: %s", krb5ccname);
1074 if (snprintf(var, sizeof(var), "KRB5CCNAME=%s", krb5ccname) == -1) {
1075 return;
1078 ret = pam_putenv(ctx->pamh, var);
1079 if (ret) {
1080 _pam_log(ctx, LOG_ERR,
1081 "failed to set KRB5CCNAME to %s: %s",
1082 var, pam_strerror(ctx->pamh, ret));
1087 * Set string into the PAM stack.
1089 * @param ctx PAM winbind context.
1090 * @param data_name Key name for pam_set_data.
1091 * @param value String value.
1093 * @return void.
1096 static void _pam_set_data_string(struct pwb_context *ctx,
1097 const char *data_name,
1098 const char *value)
1100 int ret;
1102 if (!data_name || !value || (strlen(data_name) == 0) ||
1103 (strlen(value) == 0)) {
1104 return;
1107 ret = pam_set_data(ctx->pamh, data_name, (void *)strdup(value),
1108 _pam_winbind_cleanup_func);
1109 if (ret) {
1110 _pam_log_debug(ctx, LOG_DEBUG,
1111 "Could not set data %s: %s\n",
1112 data_name, pam_strerror(ctx->pamh, ret));
1118 * Set info3 strings into the PAM stack.
1120 * @param ctx PAM winbind context.
1121 * @param data_name Key name for pam_set_data.
1122 * @param value String value.
1124 * @return void.
1127 static void _pam_set_data_info3(struct pwb_context *ctx,
1128 struct winbindd_response *response)
1130 _pam_set_data_string(ctx, PAM_WINBIND_HOMEDIR,
1131 response->data.auth.info3.home_dir);
1132 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSCRIPT,
1133 response->data.auth.info3.logon_script);
1134 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSERVER,
1135 response->data.auth.info3.logon_srv);
1136 _pam_set_data_string(ctx, PAM_WINBIND_PROFILEPATH,
1137 response->data.auth.info3.profile_path);
1141 * Free info3 strings in the PAM stack.
1143 * @param pamh PAM handle
1145 * @return void.
1148 static void _pam_free_data_info3(pam_handle_t *pamh)
1150 pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL);
1151 pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL);
1152 pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL);
1153 pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL);
1157 * Send PAM_ERROR_MSG for cached or grace logons.
1159 * @param ctx PAM winbind context.
1160 * @param username User in PAM request.
1161 * @param info3_user_flgs Info3 flags containing logon type bits.
1163 * @return void.
1166 static void _pam_warn_logon_type(struct pwb_context *ctx,
1167 const char *username,
1168 uint32_t info3_user_flgs)
1170 /* inform about logon type */
1171 if (PAM_WB_GRACE_LOGON(info3_user_flgs)) {
1173 _make_remark(ctx, PAM_ERROR_MSG,
1174 "Grace login. "
1175 "Please change your password as soon you're "
1176 "online again");
1177 _pam_log_debug(ctx, LOG_DEBUG,
1178 "User %s logged on using grace logon\n",
1179 username);
1181 } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) {
1183 _make_remark(ctx, PAM_ERROR_MSG,
1184 "Domain Controller unreachable, "
1185 "using cached credentials instead. "
1186 "Network resources may be unavailable");
1187 _pam_log_debug(ctx, LOG_DEBUG,
1188 "User %s logged on using cached credentials\n",
1189 username);
1194 * Send PAM_ERROR_MSG for krb5 errors.
1196 * @param ctx PAM winbind context.
1197 * @param username User in PAM request.
1198 * @param info3_user_flgs Info3 flags containing logon type bits.
1200 * @return void.
1203 static void _pam_warn_krb5_failure(struct pwb_context *ctx,
1204 const char *username,
1205 uint32_t info3_user_flgs)
1207 if (PAM_WB_KRB5_CLOCK_SKEW(info3_user_flgs)) {
1208 _make_remark(ctx, PAM_ERROR_MSG,
1209 "Failed to establish your Kerberos Ticket cache "
1210 "due time differences\n"
1211 "with the domain controller. "
1212 "Please verify the system time.\n");
1213 _pam_log_debug(ctx, LOG_DEBUG,
1214 "User %s: Clock skew when getting Krb5 TGT\n",
1215 username);
1220 * Compose Password Restriction String for a PAM_ERROR_MSG conversation.
1222 * @param response The struct winbindd_response.
1224 * @return string (caller needs to free).
1227 static char *_pam_compose_pwd_restriction_string(struct winbindd_response *response)
1229 char *str = NULL;
1230 size_t offset = 0, ret = 0, str_size = 1024;
1232 str = (char *)malloc(str_size);
1233 if (!str) {
1234 return NULL;
1237 memset(str, '\0', str_size);
1239 offset = snprintf(str, str_size, "Your password ");
1240 if (offset == -1) {
1241 goto failed;
1244 if (response->data.auth.policy.min_length_password > 0) {
1245 ret = snprintf(str+offset, str_size-offset,
1246 "must be at least %d characters; ",
1247 response->data.auth.policy.min_length_password);
1248 if (ret == -1) {
1249 goto failed;
1251 offset += ret;
1254 if (response->data.auth.policy.password_history > 0) {
1255 ret = snprintf(str+offset, str_size-offset,
1256 "cannot repeat any of your previous %d "
1257 "passwords; ",
1258 response->data.auth.policy.password_history);
1259 if (ret == -1) {
1260 goto failed;
1262 offset += ret;
1265 if (response->data.auth.policy.password_properties &
1266 DOMAIN_PASSWORD_COMPLEX) {
1267 ret = snprintf(str+offset, str_size-offset,
1268 "must contain capitals, numerals "
1269 "or punctuation; "
1270 "and cannot contain your account "
1271 "or full name; ");
1272 if (ret == -1) {
1273 goto failed;
1275 offset += ret;
1278 ret = snprintf(str+offset, str_size-offset,
1279 "Please type a different password. "
1280 "Type a password which meets these requirements in "
1281 "both text boxes.");
1282 if (ret == -1) {
1283 goto failed;
1286 return str;
1288 failed:
1289 SAFE_FREE(str);
1290 return NULL;
1293 /* talk to winbindd */
1294 static int winbind_auth_request(struct pwb_context *ctx,
1295 const char *user,
1296 const char *pass,
1297 const char *member,
1298 const char *cctype,
1299 const int warn_pwd_expire,
1300 struct winbindd_response *p_response,
1301 time_t *pwd_last_set,
1302 char **user_ret)
1304 struct winbindd_request request;
1305 struct winbindd_response response;
1306 int ret;
1307 bool already_expired = false;
1309 ZERO_STRUCT(request);
1310 ZERO_STRUCT(response);
1312 if (pwd_last_set) {
1313 *pwd_last_set = 0;
1316 strncpy(request.data.auth.user, user,
1317 sizeof(request.data.auth.user)-1);
1319 strncpy(request.data.auth.pass, pass,
1320 sizeof(request.data.auth.pass)-1);
1322 request.data.auth.krb5_cc_type[0] = '\0';
1323 request.data.auth.uid = -1;
1325 request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_GET_PWD_POLICY;
1327 /* Krb5 auth always has to go against the KDC of the user's realm */
1329 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1330 request.flags |= WBFLAG_PAM_CONTACT_TRUSTDOM;
1333 if (ctx->ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) {
1334 struct passwd *pwd = NULL;
1336 pwd = getpwnam(user);
1337 if (pwd == NULL) {
1338 return PAM_USER_UNKNOWN;
1340 request.data.auth.uid = pwd->pw_uid;
1343 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1345 _pam_log_debug(ctx, LOG_DEBUG,
1346 "enabling krb5 login flag\n");
1348 request.flags |= WBFLAG_PAM_KRB5 |
1349 WBFLAG_PAM_FALLBACK_AFTER_KRB5;
1352 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1353 _pam_log_debug(ctx, LOG_DEBUG,
1354 "enabling cached login flag\n");
1355 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1358 if (user_ret) {
1359 *user_ret = NULL;
1360 request.flags |= WBFLAG_PAM_UNIX_NAME;
1363 if (cctype != NULL) {
1364 strncpy(request.data.auth.krb5_cc_type, cctype,
1365 sizeof(request.data.auth.krb5_cc_type) - 1);
1366 _pam_log_debug(ctx, LOG_DEBUG,
1367 "enabling request for a %s krb5 ccache\n",
1368 cctype);
1371 request.data.auth.require_membership_of_sid[0] = '\0';
1373 if (member != NULL) {
1375 if (!winbind_name_list_to_sid_string_list(ctx, user,
1376 member,
1377 request.data.auth.require_membership_of_sid,
1378 sizeof(request.data.auth.require_membership_of_sid))) {
1380 _pam_log_debug(ctx, LOG_ERR,
1381 "failed to serialize membership of sid "
1382 "\"%s\"\n", member);
1383 return PAM_AUTH_ERR;
1387 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_AUTH,
1388 &request, &response, user);
1390 if (pwd_last_set) {
1391 *pwd_last_set = response.data.auth.info3.pass_last_set_time;
1394 if (p_response) {
1395 /* We want to process the response in the caller. */
1396 *p_response = response;
1397 return ret;
1400 if (ret) {
1401 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1402 "NT_STATUS_PASSWORD_EXPIRED");
1403 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1404 "NT_STATUS_PASSWORD_MUST_CHANGE");
1405 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1406 "NT_STATUS_INVALID_WORKSTATION");
1407 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1408 "NT_STATUS_INVALID_LOGON_HOURS");
1409 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1410 "NT_STATUS_ACCOUNT_EXPIRED");
1411 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1412 "NT_STATUS_ACCOUNT_DISABLED");
1413 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1414 "NT_STATUS_ACCOUNT_LOCKED_OUT");
1415 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1416 "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT");
1417 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1418 "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT");
1419 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1420 "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT");
1421 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1422 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1423 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1424 "NT_STATUS_NO_LOGON_SERVERS");
1425 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1426 "NT_STATUS_WRONG_PASSWORD");
1427 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1428 "NT_STATUS_ACCESS_DENIED");
1431 if (ret == PAM_SUCCESS) {
1433 /* warn a user if the password is about to expire soon */
1434 _pam_warn_password_expiry(ctx, &response,
1435 warn_pwd_expire,
1436 &already_expired);
1438 if (already_expired == true) {
1439 SMB_TIME_T last_set;
1440 last_set = response.data.auth.info3.pass_last_set_time;
1441 _pam_log_debug(ctx, LOG_DEBUG,
1442 "Password has expired "
1443 "(Password was last set: %lld, "
1444 "the policy says it should expire here "
1445 "%lld (now it's: %lu))\n",
1446 (long long int)last_set,
1447 (long long int)last_set +
1448 response.data.auth.policy.expire,
1449 time(NULL));
1451 return PAM_AUTHTOK_EXPIRED;
1454 /* inform about logon type */
1455 _pam_warn_logon_type(ctx, user,
1456 response.data.auth.info3.user_flgs);
1458 /* inform about krb5 failures */
1459 _pam_warn_krb5_failure(ctx, user,
1460 response.data.auth.info3.user_flgs);
1462 /* set some info3 info for other modules in the stack */
1463 _pam_set_data_info3(ctx, &response);
1465 /* put krb5ccname into env */
1466 _pam_setup_krb5_env(ctx, response.data.auth.krb5ccname);
1468 /* If winbindd returned a username, return the pointer to it
1469 * here. */
1470 if (user_ret && response.data.auth.unix_username[0]) {
1471 /* We have to trust it's a null terminated string. */
1472 *user_ret = strndup(response.data.auth.unix_username,
1473 sizeof(response.data.auth.unix_username) - 1);
1477 return ret;
1480 /* talk to winbindd */
1481 static int winbind_chauthtok_request(struct pwb_context *ctx,
1482 const char *user,
1483 const char *oldpass,
1484 const char *newpass,
1485 time_t pwd_last_set)
1487 struct winbindd_request request;
1488 struct winbindd_response response;
1489 int ret;
1491 ZERO_STRUCT(request);
1492 ZERO_STRUCT(response);
1494 if (request.data.chauthtok.user == NULL) {
1495 return -2;
1498 strncpy(request.data.chauthtok.user, user,
1499 sizeof(request.data.chauthtok.user) - 1);
1501 if (oldpass != NULL) {
1502 strncpy(request.data.chauthtok.oldpass, oldpass,
1503 sizeof(request.data.chauthtok.oldpass) - 1);
1504 } else {
1505 request.data.chauthtok.oldpass[0] = '\0';
1508 if (newpass != NULL) {
1509 strncpy(request.data.chauthtok.newpass, newpass,
1510 sizeof(request.data.chauthtok.newpass) - 1);
1511 } else {
1512 request.data.chauthtok.newpass[0] = '\0';
1515 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1516 request.flags = WBFLAG_PAM_KRB5 |
1517 WBFLAG_PAM_CONTACT_TRUSTDOM;
1520 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1521 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1524 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_CHAUTHTOK,
1525 &request, &response, user);
1527 if (ret == PAM_SUCCESS) {
1528 return ret;
1531 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1532 "NT_STATUS_BACKUP_CONTROLLER");
1533 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1534 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1535 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1536 "NT_STATUS_NO_LOGON_SERVERS");
1537 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1538 "NT_STATUS_ACCESS_DENIED");
1540 /* TODO: tell the min pwd length ? */
1541 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1542 "NT_STATUS_PWD_TOO_SHORT");
1544 /* TODO: tell the minage ? */
1545 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1546 "NT_STATUS_PWD_TOO_RECENT");
1548 /* TODO: tell the history length ? */
1549 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1550 "NT_STATUS_PWD_HISTORY_CONFLICT");
1552 if (!strcasecmp(response.data.auth.nt_status_string,
1553 "NT_STATUS_PASSWORD_RESTRICTION")) {
1555 char *pwd_restriction_string = NULL;
1556 SMB_TIME_T min_pwd_age;
1557 uint32_t reject_reason = response.data.auth.reject_reason;
1558 min_pwd_age = response.data.auth.policy.min_passwordage;
1560 /* FIXME: avoid to send multiple PAM messages after another */
1561 switch (reject_reason) {
1562 case -1:
1563 break;
1564 case SAMR_REJECT_OTHER:
1565 if ((min_pwd_age > 0) &&
1566 (pwd_last_set + min_pwd_age > time(NULL))) {
1567 PAM_WB_REMARK_DIRECT(ctx,
1568 "NT_STATUS_PWD_TOO_RECENT");
1570 break;
1571 case SAMR_REJECT_TOO_SHORT:
1572 PAM_WB_REMARK_DIRECT(ctx,
1573 "NT_STATUS_PWD_TOO_SHORT");
1574 break;
1575 case SAMR_REJECT_IN_HISTORY:
1576 PAM_WB_REMARK_DIRECT(ctx,
1577 "NT_STATUS_PWD_HISTORY_CONFLICT");
1578 break;
1579 case SAMR_REJECT_COMPLEXITY:
1580 _make_remark(ctx, PAM_ERROR_MSG,
1581 "Password does not meet "
1582 "complexity requirements");
1583 break;
1584 default:
1585 _pam_log_debug(ctx, LOG_DEBUG,
1586 "unknown password change "
1587 "reject reason: %d",
1588 reject_reason);
1589 break;
1592 pwd_restriction_string =
1593 _pam_compose_pwd_restriction_string(&response);
1594 if (pwd_restriction_string) {
1595 _make_remark(ctx, PAM_ERROR_MSG,
1596 pwd_restriction_string);
1597 SAFE_FREE(pwd_restriction_string);
1601 return ret;
1605 * Checks if a user has an account
1607 * return values:
1608 * 1 = User not found
1609 * 0 = OK
1610 * -1 = System error
1612 static int valid_user(struct pwb_context *ctx,
1613 const char *user)
1615 /* check not only if the user is available over NSS calls, also make
1616 * sure it's really a winbind user, this is important when stacking PAM
1617 * modules in the 'account' or 'password' facility. */
1619 struct passwd *pwd = NULL;
1620 struct winbindd_request request;
1621 struct winbindd_response response;
1622 int ret;
1624 ZERO_STRUCT(request);
1625 ZERO_STRUCT(response);
1627 pwd = getpwnam(user);
1628 if (pwd == NULL) {
1629 return 1;
1632 strncpy(request.data.username, user,
1633 sizeof(request.data.username) - 1);
1635 ret = pam_winbind_request_log(ctx, WINBINDD_GETPWNAM,
1636 &request, &response, user);
1638 switch (ret) {
1639 case PAM_USER_UNKNOWN:
1640 return 1;
1641 case PAM_SUCCESS:
1642 return 0;
1643 default:
1644 break;
1646 return -1;
1649 static char *_pam_delete(register char *xx)
1651 _pam_overwrite(xx);
1652 _pam_drop(xx);
1653 return NULL;
1657 * obtain a password from the user
1660 static int _winbind_read_password(struct pwb_context *ctx,
1661 unsigned int ctrl,
1662 const char *comment,
1663 const char *prompt1,
1664 const char *prompt2,
1665 const char **pass)
1667 int authtok_flag;
1668 int retval;
1669 const char *item;
1670 char *token;
1672 _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1675 * make sure nothing inappropriate gets returned
1678 *pass = token = NULL;
1681 * which authentication token are we getting?
1684 if (on(WINBIND__OLD_PASSWORD, ctrl)) {
1685 authtok_flag = PAM_OLDAUTHTOK;
1686 } else {
1687 authtok_flag = PAM_AUTHTOK;
1691 * should we obtain the password from a PAM item ?
1694 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) ||
1695 on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1696 retval = _pam_get_item(ctx->pamh, authtok_flag, &item);
1697 if (retval != PAM_SUCCESS) {
1698 /* very strange. */
1699 _pam_log(ctx, LOG_ALERT,
1700 "pam_get_item returned error "
1701 "to unix-read-password");
1702 return retval;
1703 } else if (item != NULL) { /* we have a password! */
1704 *pass = item;
1705 item = NULL;
1706 _pam_log(ctx, LOG_DEBUG,
1707 "pam_get_item returned a password");
1708 return PAM_SUCCESS;
1709 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1710 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
1711 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1712 && off(WINBIND__OLD_PASSWORD, ctrl)) {
1713 return PAM_AUTHTOK_RECOVER_ERR;
1717 * getting here implies we will have to get the password from the
1718 * user directly.
1722 struct pam_message msg[3], *pmsg[3];
1723 struct pam_response *resp;
1724 int i, replies;
1726 /* prepare to converse */
1728 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1729 pmsg[0] = &msg[0];
1730 msg[0].msg_style = PAM_TEXT_INFO;
1731 msg[0].msg = discard_const_p(char, comment);
1732 i = 1;
1733 } else {
1734 i = 0;
1737 pmsg[i] = &msg[i];
1738 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1739 msg[i++].msg = discard_const_p(char, prompt1);
1740 replies = 1;
1742 if (prompt2 != NULL) {
1743 pmsg[i] = &msg[i];
1744 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1745 msg[i++].msg = discard_const_p(char, prompt2);
1746 ++replies;
1748 /* so call the conversation expecting i responses */
1749 resp = NULL;
1750 retval = converse(ctx->pamh, i, pmsg, &resp);
1751 if (resp == NULL) {
1752 if (retval == PAM_SUCCESS) {
1753 retval = PAM_AUTHTOK_RECOVER_ERR;
1755 goto done;
1757 if (retval != PAM_SUCCESS) {
1758 _pam_drop_reply(resp, i);
1759 goto done;
1762 /* interpret the response */
1764 token = x_strdup(resp[i - replies].resp);
1765 if (!token) {
1766 _pam_log(ctx, LOG_NOTICE,
1767 "could not recover "
1768 "authentication token");
1769 retval = PAM_AUTHTOK_RECOVER_ERR;
1770 goto done;
1773 if (replies == 2) {
1774 /* verify that password entered correctly */
1775 if (!resp[i - 1].resp ||
1776 strcmp(token, resp[i - 1].resp)) {
1777 _pam_delete(token); /* mistyped */
1778 retval = PAM_AUTHTOK_RECOVER_ERR;
1779 _make_remark(ctx, PAM_ERROR_MSG,
1780 MISTYPED_PASS);
1785 * tidy up the conversation (resp_retcode) is ignored
1786 * -- what is it for anyway? AGM
1788 _pam_drop_reply(resp, i);
1791 done:
1792 if (retval != PAM_SUCCESS) {
1793 _pam_log_debug(ctx, LOG_DEBUG,
1794 "unable to obtain a password");
1795 return retval;
1797 /* 'token' is the entered password */
1799 /* we store this password as an item */
1801 retval = pam_set_item(ctx->pamh, authtok_flag, token);
1802 _pam_delete(token); /* clean it up */
1803 if (retval != PAM_SUCCESS ||
1804 (retval = _pam_get_item(ctx->pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1806 _pam_log(ctx, LOG_CRIT, "error manipulating password");
1807 return retval;
1811 *pass = item;
1812 item = NULL; /* break link to password */
1814 return PAM_SUCCESS;
1817 static const char *get_conf_item_string(struct pwb_context *ctx,
1818 const char *item,
1819 int config_flag)
1821 int i = 0;
1822 const char *parm_opt = NULL;
1824 if (!(ctx->ctrl & config_flag)) {
1825 goto out;
1828 /* let the pam opt take precedence over the pam_winbind.conf option */
1829 for (i=0; i<ctx->argc; i++) {
1831 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1832 char *p;
1834 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1835 _pam_log(ctx, LOG_INFO,
1836 "no \"=\" delimiter for \"%s\" found\n",
1837 item);
1838 goto out;
1840 _pam_log_debug(ctx, LOG_INFO,
1841 "PAM config: %s '%s'\n", item, p+1);
1842 return p + 1;
1846 if (ctx->dict) {
1847 char *key = NULL;
1849 if (!asprintf(&key, "global:%s", item)) {
1850 goto out;
1853 parm_opt = iniparser_getstr(ctx->dict, key);
1854 SAFE_FREE(key);
1856 _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n",
1857 item, parm_opt);
1859 out:
1860 return parm_opt;
1863 static int get_config_item_int(struct pwb_context *ctx,
1864 const char *item,
1865 int config_flag)
1867 int i, parm_opt = -1;
1869 if (!(ctx->ctrl & config_flag)) {
1870 goto out;
1873 /* let the pam opt take precedence over the pam_winbind.conf option */
1874 for (i = 0; i < ctx->argc; i++) {
1876 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1877 char *p;
1879 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1880 _pam_log(ctx, LOG_INFO,
1881 "no \"=\" delimiter for \"%s\" found\n",
1882 item);
1883 goto out;
1885 parm_opt = atoi(p + 1);
1886 _pam_log_debug(ctx, LOG_INFO,
1887 "PAM config: %s '%d'\n",
1888 item, parm_opt);
1889 return parm_opt;
1893 if (ctx->dict) {
1894 char *key = NULL;
1896 if (!asprintf(&key, "global:%s", item)) {
1897 goto out;
1900 parm_opt = iniparser_getint(ctx->dict, key, -1);
1901 SAFE_FREE(key);
1903 _pam_log_debug(ctx, LOG_INFO,
1904 "CONFIG file: %s '%d'\n",
1905 item, parm_opt);
1907 out:
1908 return parm_opt;
1911 static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx)
1913 return get_conf_item_string(ctx, "krb5_ccache_type",
1914 WINBIND_KRB5_CCACHE_TYPE);
1917 static const char *get_member_from_config(struct pwb_context *ctx)
1919 const char *ret = NULL;
1920 ret = get_conf_item_string(ctx, "require_membership_of",
1921 WINBIND_REQUIRED_MEMBERSHIP);
1922 if (ret) {
1923 return ret;
1925 return get_conf_item_string(ctx, "require-membership-of",
1926 WINBIND_REQUIRED_MEMBERSHIP);
1929 static int get_warn_pwd_expire_from_config(struct pwb_context *ctx)
1931 int ret;
1932 ret = get_config_item_int(ctx, "warn_pwd_expire",
1933 WINBIND_WARN_PWD_EXPIRE);
1934 /* no or broken setting */
1935 if (ret <= 0) {
1936 return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES;
1938 return ret;
1942 * Retrieve the winbind separator.
1944 * @param ctx PAM winbind context.
1946 * @return string separator character. NULL on failure.
1949 static char winbind_get_separator(struct pwb_context *ctx)
1951 struct winbindd_request request;
1952 struct winbindd_response response;
1954 ZERO_STRUCT(request);
1955 ZERO_STRUCT(response);
1957 if (pam_winbind_request_log(ctx, WINBINDD_INFO,
1958 &request, &response, NULL)) {
1959 return '\0';
1962 return response.data.info.winbind_separator;
1966 * Convert a upn to a name.
1968 * @param ctx PAM winbind context.
1969 * @param upn USer UPN to be trabslated.
1971 * @return converted name. NULL pointer on failure. Caller needs to free.
1974 static char* winbind_upn_to_username(struct pwb_context *ctx,
1975 const char *upn)
1977 struct winbindd_request req;
1978 struct winbindd_response resp;
1979 int retval;
1980 char *account_name;
1981 int account_name_len;
1982 char sep;
1984 /* This cannot work when the winbind separator = @ */
1986 sep = winbind_get_separator(ctx);
1987 if (!sep || sep == '@') {
1988 return NULL;
1991 /* Convert the UPN to a SID */
1993 ZERO_STRUCT(req);
1994 ZERO_STRUCT(resp);
1996 strncpy(req.data.name.dom_name, "",
1997 sizeof(req.data.name.dom_name) - 1);
1998 strncpy(req.data.name.name, upn,
1999 sizeof(req.data.name.name) - 1);
2000 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
2001 &req, &resp, upn);
2002 if (retval != PAM_SUCCESS) {
2003 return NULL;
2006 /* Convert the the SID back to the sAMAccountName */
2008 ZERO_STRUCT(req);
2009 strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
2010 ZERO_STRUCT(resp);
2011 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPSID,
2012 &req, &resp, upn);
2013 if (retval != PAM_SUCCESS) {
2014 return NULL;
2017 account_name_len = asprintf(&account_name, "%s\\%s",
2018 resp.data.name.dom_name,
2019 resp.data.name.name);
2021 return account_name;
2024 PAM_EXTERN
2025 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
2026 int argc, const char **argv)
2028 const char *username;
2029 const char *password;
2030 const char *member = NULL;
2031 const char *cctype = NULL;
2032 int warn_pwd_expire;
2033 int retval = PAM_AUTH_ERR;
2034 char *username_ret = NULL;
2035 char *new_authtok_required = NULL;
2036 char *real_username = NULL;
2037 struct pwb_context *ctx = NULL;
2039 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2040 if (retval) {
2041 goto out;
2044 _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx);
2046 /* Get the username */
2047 retval = pam_get_user(pamh, &username, NULL);
2048 if ((retval != PAM_SUCCESS) || (!username)) {
2049 _pam_log_debug(ctx, LOG_DEBUG,
2050 "can not get the username");
2051 retval = PAM_SERVICE_ERR;
2052 goto out;
2056 #if defined(AIX)
2057 /* Decode the user name since AIX does not support logn user
2058 names by default. The name is encoded as _#uid. */
2060 if (username[0] == '_') {
2061 uid_t id = atoi(&username[1]);
2062 struct passwd *pw = NULL;
2064 if ((id!=0) && ((pw = getpwuid(id)) != NULL)) {
2065 real_username = strdup(pw->pw_name);
2068 #endif
2070 if (!real_username) {
2071 /* Just making a copy of the username we got from PAM */
2072 if ((real_username = strdup(username)) == NULL) {
2073 _pam_log_debug(ctx, LOG_DEBUG,
2074 "memory allocation failure when copying "
2075 "username");
2076 retval = PAM_SERVICE_ERR;
2077 goto out;
2081 /* Maybe this was a UPN */
2083 if (strchr(real_username, '@') != NULL) {
2084 char *samaccountname = NULL;
2086 samaccountname = winbind_upn_to_username(ctx,
2087 real_username);
2088 if (samaccountname) {
2089 free(real_username);
2090 real_username = samaccountname;
2094 retval = _winbind_read_password(ctx, ctx->ctrl, NULL,
2095 "Password: ", NULL,
2096 &password);
2098 if (retval != PAM_SUCCESS) {
2099 _pam_log(ctx, LOG_ERR,
2100 "Could not retrieve user's password");
2101 retval = PAM_AUTHTOK_ERR;
2102 goto out;
2105 /* Let's not give too much away in the log file */
2107 #ifdef DEBUG_PASSWORD
2108 _pam_log_debug(ctx, LOG_INFO,
2109 "Verify user '%s' with password '%s'",
2110 real_username, password);
2111 #else
2112 _pam_log_debug(ctx, LOG_INFO,
2113 "Verify user '%s'", real_username);
2114 #endif
2116 member = get_member_from_config(ctx);
2117 cctype = get_krb5_cc_type_from_config(ctx);
2118 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2120 /* Now use the username to look up password */
2121 retval = winbind_auth_request(ctx, real_username, password,
2122 member, cctype, warn_pwd_expire, NULL,
2123 NULL, &username_ret);
2125 if (retval == PAM_NEW_AUTHTOK_REQD ||
2126 retval == PAM_AUTHTOK_EXPIRED) {
2128 char *new_authtok_required_during_auth = NULL;
2130 if (!asprintf(&new_authtok_required, "%d", retval)) {
2131 retval = PAM_BUF_ERR;
2132 goto out;
2135 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2136 new_authtok_required,
2137 _pam_winbind_cleanup_func);
2139 retval = PAM_SUCCESS;
2141 if (!asprintf(&new_authtok_required_during_auth, "%d", true)) {
2142 retval = PAM_BUF_ERR;
2143 goto out;
2146 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2147 new_authtok_required_during_auth,
2148 _pam_winbind_cleanup_func);
2150 goto out;
2153 out:
2154 if (username_ret) {
2155 pam_set_item (pamh, PAM_USER, username_ret);
2156 _pam_log_debug(ctx, LOG_INFO,
2157 "Returned user was '%s'", username_ret);
2158 free(username_ret);
2161 if (real_username) {
2162 free(real_username);
2165 if (!new_authtok_required) {
2166 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
2169 if (retval != PAM_SUCCESS) {
2170 _pam_free_data_info3(pamh);
2173 _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
2175 _pam_winbind_free_context(ctx);
2177 return retval;
2180 PAM_EXTERN
2181 int pam_sm_setcred(pam_handle_t *pamh, int flags,
2182 int argc, const char **argv)
2184 int ret = PAM_SYSTEM_ERR;
2185 struct pwb_context *ctx = NULL;
2187 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2188 if (ret) {
2189 goto out;
2192 _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx);
2194 switch (flags & ~PAM_SILENT) {
2196 case PAM_DELETE_CRED:
2197 ret = pam_sm_close_session(pamh, flags, argc, argv);
2198 break;
2199 case PAM_REFRESH_CRED:
2200 _pam_log_debug(ctx, LOG_WARNING,
2201 "PAM_REFRESH_CRED not implemented");
2202 ret = PAM_SUCCESS;
2203 break;
2204 case PAM_REINITIALIZE_CRED:
2205 _pam_log_debug(ctx, LOG_WARNING,
2206 "PAM_REINITIALIZE_CRED not implemented");
2207 ret = PAM_SUCCESS;
2208 break;
2209 case PAM_ESTABLISH_CRED:
2210 _pam_log_debug(ctx, LOG_WARNING,
2211 "PAM_ESTABLISH_CRED not implemented");
2212 ret = PAM_SUCCESS;
2213 break;
2214 default:
2215 ret = PAM_SYSTEM_ERR;
2216 break;
2219 out:
2221 _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret);
2223 _pam_winbind_free_context(ctx);
2225 return ret;
2229 * Account management. We want to verify that the account exists
2230 * before returning PAM_SUCCESS
2232 PAM_EXTERN
2233 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
2234 int argc, const char **argv)
2236 const char *username;
2237 int ret = PAM_USER_UNKNOWN;
2238 void *tmp = NULL;
2239 struct pwb_context *ctx = NULL;
2241 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2242 if (ret) {
2243 goto out;
2246 _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx);
2249 /* Get the username */
2250 ret = pam_get_user(pamh, &username, NULL);
2251 if ((ret != PAM_SUCCESS) || (!username)) {
2252 _pam_log_debug(ctx, LOG_DEBUG,
2253 "can not get the username");
2254 ret = PAM_SERVICE_ERR;
2255 goto out;
2258 /* Verify the username */
2259 ret = valid_user(ctx, username);
2260 switch (ret) {
2261 case -1:
2262 /* some sort of system error. The log was already printed */
2263 ret = PAM_SERVICE_ERR;
2264 goto out;
2265 case 1:
2266 /* the user does not exist */
2267 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
2268 username);
2269 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
2270 ret = PAM_IGNORE;
2271 goto out;
2273 ret = PAM_USER_UNKNOWN;
2274 goto out;
2275 case 0:
2276 pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2277 (const void **)&tmp);
2278 if (tmp != NULL) {
2279 ret = atoi((const char *)tmp);
2280 switch (ret) {
2281 case PAM_AUTHTOK_EXPIRED:
2282 /* fall through, since new token is required in this case */
2283 case PAM_NEW_AUTHTOK_REQD:
2284 _pam_log(ctx, LOG_WARNING,
2285 "pam_sm_acct_mgmt success but %s is set",
2286 PAM_WINBIND_NEW_AUTHTOK_REQD);
2287 _pam_log(ctx, LOG_NOTICE,
2288 "user '%s' needs new password",
2289 username);
2290 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
2291 ret = PAM_NEW_AUTHTOK_REQD;
2292 goto out;
2293 default:
2294 _pam_log(ctx, LOG_WARNING,
2295 "pam_sm_acct_mgmt success");
2296 _pam_log(ctx, LOG_NOTICE,
2297 "user '%s' granted access", username);
2298 ret = PAM_SUCCESS;
2299 goto out;
2303 /* Otherwise, the authentication looked good */
2304 _pam_log(ctx, LOG_NOTICE,
2305 "user '%s' granted access", username);
2306 ret = PAM_SUCCESS;
2307 goto out;
2308 default:
2309 /* we don't know anything about this return value */
2310 _pam_log(ctx, LOG_ERR,
2311 "internal module error (ret = %d, user = '%s')",
2312 ret, username);
2313 ret = PAM_SERVICE_ERR;
2314 goto out;
2317 /* should not be reached */
2318 ret = PAM_IGNORE;
2320 out:
2322 _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret);
2324 _pam_winbind_free_context(ctx);
2326 return ret;
2329 PAM_EXTERN
2330 int pam_sm_open_session(pam_handle_t *pamh, int flags,
2331 int argc, const char **argv)
2333 int ret = PAM_SYSTEM_ERR;
2334 struct pwb_context *ctx = NULL;
2336 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2337 if (ret) {
2338 goto out;
2341 _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx);
2343 ret = PAM_SUCCESS;
2345 out:
2346 _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret);
2348 _pam_winbind_free_context(ctx);
2350 return ret;
2353 PAM_EXTERN
2354 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2355 int argc, const char **argv)
2357 int retval = PAM_SUCCESS;
2358 struct pwb_context *ctx = NULL;
2360 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2361 if (retval) {
2362 goto out;
2365 _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx);
2367 if (!(flags & PAM_DELETE_CRED)) {
2368 retval = PAM_SUCCESS;
2369 goto out;
2372 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2374 /* destroy the ccache here */
2375 struct winbindd_request request;
2376 struct winbindd_response response;
2377 const char *user;
2378 const char *ccname = NULL;
2379 struct passwd *pwd = NULL;
2381 ZERO_STRUCT(request);
2382 ZERO_STRUCT(response);
2384 retval = pam_get_user(pamh, &user, "Username: ");
2385 if (retval) {
2386 _pam_log(ctx, LOG_ERR,
2387 "could not identify user");
2388 goto out;
2391 if (user == NULL) {
2392 _pam_log(ctx, LOG_ERR,
2393 "username was NULL!");
2394 retval = PAM_USER_UNKNOWN;
2395 goto out;
2398 _pam_log_debug(ctx, LOG_DEBUG,
2399 "username [%s] obtained", user);
2401 ccname = pam_getenv(pamh, "KRB5CCNAME");
2402 if (ccname == NULL) {
2403 _pam_log_debug(ctx, LOG_DEBUG,
2404 "user has no KRB5CCNAME environment");
2407 strncpy(request.data.logoff.user, user,
2408 sizeof(request.data.logoff.user) - 1);
2410 if (ccname) {
2411 strncpy(request.data.logoff.krb5ccname, ccname,
2412 sizeof(request.data.logoff.krb5ccname) - 1);
2415 pwd = getpwnam(user);
2416 if (pwd == NULL) {
2417 retval = PAM_USER_UNKNOWN;
2418 goto out;
2420 request.data.logoff.uid = pwd->pw_uid;
2422 request.flags = WBFLAG_PAM_KRB5 |
2423 WBFLAG_PAM_CONTACT_TRUSTDOM;
2425 retval = pam_winbind_request_log(ctx,
2426 WINBINDD_PAM_LOGOFF,
2427 &request, &response, user);
2430 out:
2432 * Delete the krb5 ccname variable from the PAM environment
2433 * if it was set by winbind.
2435 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2436 pam_putenv(pamh, "KRB5CCNAME");
2439 _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, retval);
2441 _pam_winbind_free_context(ctx);
2443 return retval;
2447 * evaluate whether we need to re-authenticate with kerberos after a
2448 * password change
2450 * @param ctx PAM winbind context.
2451 * @param user The username
2453 * @return boolean Returns true if required, false if not.
2456 static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx,
2457 const char *user)
2460 /* Make sure that we only do this if a) the chauthtok got initiated
2461 * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any
2462 * later password change via the "passwd" command if done by the user
2463 * itself
2464 * NB. If we login from gdm or xdm and the password expires,
2465 * we change the password, but there is no memory cache.
2466 * Thus, even for passthrough login, we should do the
2467 * authentication again to update memory cache.
2468 * --- BoYang
2469 * */
2471 char *new_authtok_reqd_during_auth = NULL;
2472 struct passwd *pwd = NULL;
2474 _pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2475 &new_authtok_reqd_during_auth);
2476 pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2477 NULL, NULL);
2479 if (new_authtok_reqd_during_auth) {
2480 return true;
2483 pwd = getpwnam(user);
2484 if (!pwd) {
2485 return false;
2488 if (getuid() == pwd->pw_uid) {
2489 return true;
2492 return false;
2496 PAM_EXTERN
2497 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2498 int argc, const char **argv)
2500 unsigned int lctrl;
2501 int ret;
2502 bool cached_login = false;
2504 /* <DO NOT free() THESE> */
2505 const char *user;
2506 char *pass_old, *pass_new;
2507 /* </DO NOT free() THESE> */
2509 char *Announce;
2511 int retry = 0;
2512 char *username_ret = NULL;
2513 struct winbindd_response response;
2514 struct pwb_context *ctx = NULL;
2516 ZERO_STRUCT(response);
2518 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2519 if (ret) {
2520 goto out;
2523 _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx);
2525 cached_login = (ctx->ctrl & WINBIND_CACHED_LOGIN);
2527 /* clearing offline bit for auth */
2528 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2531 * First get the name of a user
2533 ret = pam_get_user(pamh, &user, "Username: ");
2534 if (ret) {
2535 _pam_log(ctx, LOG_ERR,
2536 "password - could not identify user");
2537 goto out;
2540 if (user == NULL) {
2541 _pam_log(ctx, LOG_ERR, "username was NULL!");
2542 ret = PAM_USER_UNKNOWN;
2543 goto out;
2546 _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user);
2548 /* check if this is really a user in winbindd, not only in NSS */
2549 ret = valid_user(ctx, user);
2550 switch (ret) {
2551 case 1:
2552 ret = PAM_USER_UNKNOWN;
2553 goto out;
2554 case -1:
2555 ret = PAM_SYSTEM_ERR;
2556 goto out;
2557 default:
2558 break;
2562 * obtain and verify the current password (OLDAUTHTOK) for
2563 * the user.
2566 if (flags & PAM_PRELIM_CHECK) {
2567 time_t pwdlastset_prelim = 0;
2569 /* instruct user what is happening */
2570 #define greeting "Changing password for "
2571 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2572 if (Announce == NULL) {
2573 _pam_log(ctx, LOG_CRIT,
2574 "password - out of memory");
2575 ret = PAM_BUF_ERR;
2576 goto out;
2578 (void) strcpy(Announce, greeting);
2579 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2580 #undef greeting
2582 lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD;
2583 ret = _winbind_read_password(ctx, lctrl,
2584 Announce,
2585 "(current) NT password: ",
2586 NULL,
2587 (const char **) &pass_old);
2588 if (ret != PAM_SUCCESS) {
2589 _pam_log(ctx, LOG_NOTICE,
2590 "password - (old) token not obtained");
2591 goto out;
2594 /* verify that this is the password for this user */
2596 ret = winbind_auth_request(ctx, user, pass_old,
2597 NULL, NULL, 0, &response,
2598 &pwdlastset_prelim, NULL);
2600 if (ret != PAM_ACCT_EXPIRED &&
2601 ret != PAM_AUTHTOK_EXPIRED &&
2602 ret != PAM_NEW_AUTHTOK_REQD &&
2603 ret != PAM_SUCCESS) {
2604 pass_old = NULL;
2605 goto out;
2608 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2609 (void *)pwdlastset_prelim, NULL);
2611 ret = pam_set_item(pamh, PAM_OLDAUTHTOK,
2612 (const void *) pass_old);
2613 pass_old = NULL;
2614 if (ret != PAM_SUCCESS) {
2615 _pam_log(ctx, LOG_CRIT,
2616 "failed to set PAM_OLDAUTHTOK");
2618 } else if (flags & PAM_UPDATE_AUTHTOK) {
2620 time_t pwdlastset_update = 0;
2623 * obtain the proposed password
2627 * get the old token back.
2630 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2632 if (ret != PAM_SUCCESS) {
2633 _pam_log(ctx, LOG_NOTICE,
2634 "user not authenticated");
2635 goto out;
2638 lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2640 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2641 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2643 retry = 0;
2644 ret = PAM_AUTHTOK_ERR;
2645 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2647 * use_authtok is to force the use of a previously entered
2648 * password -- needed for pluggable password strength checking
2651 ret = _winbind_read_password(ctx, lctrl,
2652 NULL,
2653 "Enter new NT password: ",
2654 "Retype new NT password: ",
2655 (const char **)&pass_new);
2657 if (ret != PAM_SUCCESS) {
2658 _pam_log_debug(ctx, LOG_ALERT,
2659 "password - "
2660 "new password not obtained");
2661 pass_old = NULL;/* tidy up */
2662 goto out;
2666 * At this point we know who the user is and what they
2667 * propose as their new password. Verify that the new
2668 * password is acceptable.
2671 if (pass_new[0] == '\0') {/* "\0" password = NULL */
2672 pass_new = NULL;
2677 * By reaching here we have approved the passwords and must now
2678 * rebuild the password database file.
2680 _pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2681 &pwdlastset_update);
2684 * if cached creds were enabled, make sure to set the
2685 * WINBIND_CACHED_LOGIN bit here in order to have winbindd
2686 * update the cached creds storage - gd
2688 if (cached_login) {
2689 ctx->ctrl |= WINBIND_CACHED_LOGIN;
2692 ret = winbind_chauthtok_request(ctx, user, pass_old,
2693 pass_new, pwdlastset_update);
2694 if (ret) {
2695 _pam_overwrite(pass_new);
2696 _pam_overwrite(pass_old);
2697 pass_old = pass_new = NULL;
2698 goto out;
2701 if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) {
2703 const char *member = NULL;
2704 const char *cctype = NULL;
2705 int warn_pwd_expire;
2707 member = get_member_from_config(ctx);
2708 cctype = get_krb5_cc_type_from_config(ctx);
2709 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2711 /* Keep WINBIND_CACHED_LOGIN bit for
2712 * authentication after changing the password.
2713 * This will update the cached credentials in case
2714 * that winbindd_dual_pam_chauthtok() fails
2715 * to update them.
2716 * --- BoYang
2717 * */
2719 ret = winbind_auth_request(ctx, user, pass_new,
2720 member, cctype, 0, &response,
2721 NULL, &username_ret);
2722 _pam_overwrite(pass_new);
2723 _pam_overwrite(pass_old);
2724 pass_old = pass_new = NULL;
2726 if (ret == PAM_SUCCESS) {
2728 /* warn a user if the password is about to
2729 * expire soon */
2730 _pam_warn_password_expiry(ctx, &response,
2731 warn_pwd_expire,
2732 NULL);
2734 /* set some info3 info for other modules in the
2735 * stack */
2736 _pam_set_data_info3(ctx, &response);
2738 /* put krb5ccname into env */
2739 _pam_setup_krb5_env(ctx,
2740 response.data.auth.krb5ccname);
2742 if (username_ret) {
2743 pam_set_item(pamh, PAM_USER,
2744 username_ret);
2745 _pam_log_debug(ctx, LOG_INFO,
2746 "Returned user was '%s'",
2747 username_ret);
2748 free(username_ret);
2752 goto out;
2754 } else {
2755 ret = PAM_SERVICE_ERR;
2758 out:
2760 /* Deal with offline errors. */
2761 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2762 "NT_STATUS_NO_LOGON_SERVERS");
2763 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2764 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2765 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2766 "NT_STATUS_ACCESS_DENIED");
2768 _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);
2770 _pam_winbind_free_context(ctx);
2772 return ret;
2775 #ifdef PAM_STATIC
2777 /* static module data */
2779 struct pam_module _pam_winbind_modstruct = {
2780 MODULE_NAME,
2781 pam_sm_authenticate,
2782 pam_sm_setcred,
2783 pam_sm_acct_mgmt,
2784 pam_sm_open_session,
2785 pam_sm_close_session,
2786 pam_sm_chauthtok
2789 #endif
2792 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
2793 * Copyright (c) Tim Potter <tpot@samba.org> 2000
2794 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2795 * Copyright (c) Guenther Deschner <gd@samba.org> 2005-2008
2796 * Copyright (c) Jan Rêkorajski 1999.
2797 * Copyright (c) Andrew G. Morgan 1996-8.
2798 * Copyright (c) Alex O. Yuriev, 1996.
2799 * Copyright (c) Cristian Gafton 1996.
2800 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
2802 * Redistribution and use in source and binary forms, with or without
2803 * modification, are permitted provided that the following conditions
2804 * are met:
2805 * 1. Redistributions of source code must retain the above copyright
2806 * notice, and the entire permission notice in its entirety,
2807 * including the disclaimer of warranties.
2808 * 2. Redistributions in binary form must reproduce the above copyright
2809 * notice, this list of conditions and the following disclaimer in the
2810 * documentation and/or other materials provided with the distribution.
2811 * 3. The name of the author may not be used to endorse or promote
2812 * products derived from this software without specific prior
2813 * written permission.
2815 * ALTERNATIVELY, this product may be distributed under the terms of
2816 * the GNU Public License, in which case the provisions of the GPL are
2817 * required INSTEAD OF the above restrictions. (This clause is
2818 * necessary due to a potential bad interaction between the GPL and
2819 * the restrictions contained in a BSD-style copyright.)
2821 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2822 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2823 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2824 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2825 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2826 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2827 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2828 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2829 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2830 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2831 * OF THE POSSIBILITY OF SUCH DAMAGE.