pam_winbind: Update cached creds during password change.
[Samba.git] / source / nsswitch / pam_winbind.c
blobe42199cd0f693ea594358648d92cd1d1412154f3
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 #define _PAM_LOG_FUNCTION_ENTER(function, ctx) \
16 do { \
17 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] ENTER: " \
18 function " (flags: 0x%04x)", ctx->pamh, ctx->flags); \
19 _pam_log_state(ctx); \
20 } while (0)
22 #define _PAM_LOG_FUNCTION_LEAVE(function, ctx, retval) \
23 do { \
24 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] LEAVE: " \
25 function " returning %d", ctx->pamh, retval); \
26 _pam_log_state(ctx); \
27 } while (0)
29 /* data tokens */
31 #define MAX_PASSWD_TRIES 3
34 * Work around the pam API that has functions with void ** as parameters
35 * These lead to strict aliasing warnings with gcc.
37 static int _pam_get_item(const pam_handle_t *pamh,
38 int item_type,
39 const void *_item)
41 const void **item = (const void **)_item;
42 return pam_get_item(pamh, item_type, item);
44 static int _pam_get_data(const pam_handle_t *pamh,
45 const char *module_data_name,
46 const void *_data)
48 const void **data = (const void **)_data;
49 return pam_get_data(pamh, module_data_name, data);
52 /* some syslogging */
54 #ifdef HAVE_PAM_VSYSLOG
55 static void _pam_log_int(const pam_handle_t *pamh,
56 int err,
57 const char *format,
58 va_list args)
60 pam_vsyslog(pamh, err, format, args);
62 #else
63 static void _pam_log_int(const pam_handle_t *pamh,
64 int err,
65 const char *format,
66 va_list args)
68 char *format2 = NULL;
69 const char *service;
71 _pam_get_item(pamh, PAM_SERVICE, &service);
73 format2 = (char *)malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
74 if (format2 == NULL) {
75 /* what else todo ? */
76 vsyslog(err, format, args);
77 return;
80 sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
81 vsyslog(err, format2, args);
82 SAFE_FREE(format2);
84 #endif /* HAVE_PAM_VSYSLOG */
86 static bool _pam_log_is_silent(int ctrl)
88 return on(ctrl, WINBIND_SILENT);
91 static void _pam_log(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
92 static void _pam_log(struct pwb_context *r, int err, const char *format, ...)
94 va_list args;
96 if (_pam_log_is_silent(r->ctrl)) {
97 return;
100 va_start(args, format);
101 _pam_log_int(r->pamh, err, format, args);
102 va_end(args);
104 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
105 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
107 va_list args;
109 if (_pam_log_is_silent(ctrl)) {
110 return;
113 va_start(args, format);
114 _pam_log_int(pamh, err, format, args);
115 va_end(args);
118 static bool _pam_log_is_debug_enabled(int ctrl)
120 if (ctrl == -1) {
121 return false;
124 if (_pam_log_is_silent(ctrl)) {
125 return false;
128 if (!(ctrl & WINBIND_DEBUG_ARG)) {
129 return false;
132 return true;
135 static bool _pam_log_is_debug_state_enabled(int ctrl)
137 if (!(ctrl & WINBIND_DEBUG_STATE)) {
138 return false;
141 return _pam_log_is_debug_enabled(ctrl);
144 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
145 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...)
147 va_list args;
149 if (!_pam_log_is_debug_enabled(r->ctrl)) {
150 return;
153 va_start(args, format);
154 _pam_log_int(r->pamh, err, format, args);
155 va_end(args);
157 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
158 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
160 va_list args;
162 if (!_pam_log_is_debug_enabled(ctrl)) {
163 return;
166 va_start(args, format);
167 _pam_log_int(pamh, err, format, args);
168 va_end(args);
171 static void _pam_log_state_datum(struct pwb_context *ctx,
172 int item_type,
173 const char *key,
174 int is_string)
176 const void *data = NULL;
177 if (item_type != 0) {
178 pam_get_item(ctx->pamh, item_type, &data);
179 } else {
180 pam_get_data(ctx->pamh, key, &data);
182 if (data != NULL) {
183 const char *type = (item_type != 0) ? "ITEM" : "DATA";
184 if (is_string != 0) {
185 _pam_log_debug(ctx, LOG_DEBUG,
186 "[pamh: %p] STATE: %s(%s) = \"%s\" (%p)",
187 ctx->pamh, type, key, (const char *)data,
188 data);
189 } else {
190 _pam_log_debug(ctx, LOG_DEBUG,
191 "[pamh: %p] STATE: %s(%s) = %p",
192 ctx->pamh, type, key, data);
197 #define _PAM_LOG_STATE_DATA_POINTER(ctx, module_data_name) \
198 _pam_log_state_datum(ctx, 0, module_data_name, 0)
200 #define _PAM_LOG_STATE_DATA_STRING(ctx, module_data_name) \
201 _pam_log_state_datum(ctx, 0, module_data_name, 1)
203 #define _PAM_LOG_STATE_ITEM_POINTER(ctx, item_type) \
204 _pam_log_state_datum(ctx, item_type, #item_type, 0)
206 #define _PAM_LOG_STATE_ITEM_STRING(ctx, item_type) \
207 _pam_log_state_datum(ctx, item_type, #item_type, 1)
209 #ifdef DEBUG_PASSWORD
210 #define _LOG_PASSWORD_AS_STRING 1
211 #else
212 #define _LOG_PASSWORD_AS_STRING 0
213 #endif
215 #define _PAM_LOG_STATE_ITEM_PASSWORD(ctx, item_type) \
216 _pam_log_state_datum(ctx, item_type, #item_type, \
217 _LOG_PASSWORD_AS_STRING)
219 static void _pam_log_state(struct pwb_context *ctx)
221 if (!_pam_log_is_debug_state_enabled(ctx->ctrl)) {
222 return;
225 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_SERVICE);
226 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER);
227 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_TTY);
228 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RHOST);
229 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RUSER);
230 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_OLDAUTHTOK);
231 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_AUTHTOK);
232 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER_PROMPT);
233 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_CONV);
234 #ifdef PAM_FAIL_DELAY
235 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_FAIL_DELAY);
236 #endif
237 #ifdef PAM_REPOSITORY
238 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_REPOSITORY);
239 #endif
241 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_HOMEDIR);
242 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSCRIPT);
243 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSERVER);
244 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_PROFILEPATH);
245 _PAM_LOG_STATE_DATA_STRING(ctx,
246 PAM_WINBIND_NEW_AUTHTOK_REQD);
247 /* Use atoi to get PAM result code */
248 _PAM_LOG_STATE_DATA_STRING(ctx,
249 PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH);
250 _PAM_LOG_STATE_DATA_POINTER(ctx, PAM_WINBIND_PWD_LAST_SET);
253 static int _pam_parse(const pam_handle_t *pamh,
254 int flags,
255 int argc,
256 const char **argv,
257 dictionary **result_d)
259 int ctrl = 0;
260 const char *config_file = NULL;
261 int i;
262 const char **v;
263 dictionary *d = NULL;
265 if (flags & PAM_SILENT) {
266 ctrl |= WINBIND_SILENT;
269 for (i=argc,v=argv; i-- > 0; ++v) {
270 if (!strncasecmp(*v, "config", strlen("config"))) {
271 ctrl |= WINBIND_CONFIG_FILE;
272 config_file = v[i];
273 break;
277 if (config_file == NULL) {
278 config_file = PAM_WINBIND_CONFIG_FILE;
281 d = iniparser_load(config_file);
282 if (d == NULL) {
283 goto config_from_pam;
286 if (iniparser_getboolean(d, "global:debug", false)) {
287 ctrl |= WINBIND_DEBUG_ARG;
290 if (iniparser_getboolean(d, "global:debug_state", false)) {
291 ctrl |= WINBIND_DEBUG_STATE;
294 if (iniparser_getboolean(d, "global:cached_login", false)) {
295 ctrl |= WINBIND_CACHED_LOGIN;
298 if (iniparser_getboolean(d, "global:krb5_auth", false)) {
299 ctrl |= WINBIND_KRB5_AUTH;
302 if (iniparser_getboolean(d, "global:silent", false)) {
303 ctrl |= WINBIND_SILENT;
306 if (iniparser_getstr(d, "global:krb5_ccache_type") != NULL) {
307 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
310 if ((iniparser_getstr(d, "global:require-membership-of") != NULL) ||
311 (iniparser_getstr(d, "global:require_membership_of") != NULL)) {
312 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
315 if (iniparser_getboolean(d, "global:try_first_pass", false)) {
316 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
319 if (iniparser_getint(d, "global:warn_pwd_expire", 0)) {
320 ctrl |= WINBIND_WARN_PWD_EXPIRE;
323 config_from_pam:
324 /* step through arguments */
325 for (i=argc,v=argv; i-- > 0; ++v) {
327 /* generic options */
328 if (!strcmp(*v,"debug"))
329 ctrl |= WINBIND_DEBUG_ARG;
330 else if (!strcasecmp(*v, "debug_state"))
331 ctrl |= WINBIND_DEBUG_STATE;
332 else if (!strcasecmp(*v, "silent"))
333 ctrl |= WINBIND_SILENT;
334 else if (!strcasecmp(*v, "use_authtok"))
335 ctrl |= WINBIND_USE_AUTHTOK_ARG;
336 else if (!strcasecmp(*v, "use_first_pass"))
337 ctrl |= WINBIND_USE_FIRST_PASS_ARG;
338 else if (!strcasecmp(*v, "try_first_pass"))
339 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
340 else if (!strcasecmp(*v, "unknown_ok"))
341 ctrl |= WINBIND_UNKNOWN_OK_ARG;
342 else if (!strncasecmp(*v, "require_membership_of",
343 strlen("require_membership_of")))
344 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
345 else if (!strncasecmp(*v, "require-membership-of",
346 strlen("require-membership-of")))
347 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
348 else if (!strcasecmp(*v, "krb5_auth"))
349 ctrl |= WINBIND_KRB5_AUTH;
350 else if (!strncasecmp(*v, "krb5_ccache_type",
351 strlen("krb5_ccache_type")))
352 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
353 else if (!strcasecmp(*v, "cached_login"))
354 ctrl |= WINBIND_CACHED_LOGIN;
355 else {
356 __pam_log(pamh, ctrl, LOG_ERR,
357 "pam_parse: unknown option: %s", *v);
358 return -1;
363 if (result_d) {
364 *result_d = d;
365 } else {
366 if (d) {
367 iniparser_freedict(d);
371 return ctrl;
374 static void _pam_winbind_free_context(struct pwb_context *ctx)
376 if (ctx->dict) {
377 iniparser_freedict(ctx->dict);
380 SAFE_FREE(ctx);
383 static int _pam_winbind_init_context(pam_handle_t *pamh,
384 int flags,
385 int argc,
386 const char **argv,
387 struct pwb_context **ctx_p)
389 struct pwb_context *r = NULL;
391 r = (struct pwb_context *)malloc(sizeof(struct pwb_context));
392 if (!r) {
393 return PAM_BUF_ERR;
396 ZERO_STRUCTP(r);
398 r->pamh = pamh;
399 r->flags = flags;
400 r->argc = argc;
401 r->argv = argv;
402 r->ctrl = _pam_parse(pamh, flags, argc, argv, &r->dict);
403 if (r->ctrl == -1) {
404 _pam_winbind_free_context(r);
405 return PAM_SYSTEM_ERR;
408 *ctx_p = r;
410 return PAM_SUCCESS;
413 static void _pam_winbind_cleanup_func(pam_handle_t *pamh,
414 void *data,
415 int error_status)
417 int ctrl = _pam_parse(pamh, 0, 0, NULL, NULL);
418 if (_pam_log_is_debug_state_enabled(ctrl)) {
419 __pam_log_debug(pamh, ctrl, LOG_DEBUG,
420 "[pamh: %p] CLEAN: cleaning up PAM data %p "
421 "(error_status = %d)", pamh, data,
422 error_status);
424 SAFE_FREE(data);
428 static const struct ntstatus_errors {
429 const char *ntstatus_string;
430 const char *error_string;
431 } ntstatus_errors[] = {
432 {"NT_STATUS_OK",
433 "Success"},
434 {"NT_STATUS_BACKUP_CONTROLLER",
435 "No primary Domain Controler available"},
436 {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND",
437 "No domain controllers found"},
438 {"NT_STATUS_NO_LOGON_SERVERS",
439 "No logon servers"},
440 {"NT_STATUS_PWD_TOO_SHORT",
441 "Password too short"},
442 {"NT_STATUS_PWD_TOO_RECENT",
443 "The password of this user is too recent to change"},
444 {"NT_STATUS_PWD_HISTORY_CONFLICT",
445 "Password is already in password history"},
446 {"NT_STATUS_PASSWORD_EXPIRED",
447 "Your password has expired"},
448 {"NT_STATUS_PASSWORD_MUST_CHANGE",
449 "You need to change your password now"},
450 {"NT_STATUS_INVALID_WORKSTATION",
451 "You are not allowed to logon from this workstation"},
452 {"NT_STATUS_INVALID_LOGON_HOURS",
453 "You are not allowed to logon at this time"},
454 {"NT_STATUS_ACCOUNT_EXPIRED",
455 "Your account has expired. "
456 "Please contact your System administrator"}, /* SCNR */
457 {"NT_STATUS_ACCOUNT_DISABLED",
458 "Your account is disabled. "
459 "Please contact your System administrator"}, /* SCNR */
460 {"NT_STATUS_ACCOUNT_LOCKED_OUT",
461 "Your account has been locked. "
462 "Please contact your System administrator"}, /* SCNR */
463 {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
464 "Invalid Trust Account"},
465 {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT",
466 "Invalid Trust Account"},
467 {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT",
468 "Invalid Trust Account"},
469 {"NT_STATUS_ACCESS_DENIED",
470 "Access is denied"},
471 {NULL, NULL}
474 static const char *_get_ntstatus_error_string(const char *nt_status_string)
476 int i;
477 for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) {
478 if (!strcasecmp(ntstatus_errors[i].ntstatus_string,
479 nt_status_string)) {
480 return ntstatus_errors[i].error_string;
483 return NULL;
486 /* --- authentication management functions --- */
488 /* Attempt a conversation */
490 static int converse(const pam_handle_t *pamh,
491 int nargs,
492 struct pam_message **message,
493 struct pam_response **response)
495 int retval;
496 struct pam_conv *conv;
498 retval = _pam_get_item(pamh, PAM_CONV, &conv);
499 if (retval == PAM_SUCCESS) {
500 retval = conv->conv(nargs,
501 (const struct pam_message **)message,
502 response, conv->appdata_ptr);
505 return retval; /* propagate error status */
509 static int _make_remark(struct pwb_context *ctx,
510 int type,
511 const char *text)
513 int retval = PAM_SUCCESS;
515 struct pam_message *pmsg[1], msg[1];
516 struct pam_response *resp;
518 if (ctx->flags & WINBIND_SILENT) {
519 return PAM_SUCCESS;
522 pmsg[0] = &msg[0];
523 msg[0].msg = discard_const_p(char, text);
524 msg[0].msg_style = type;
526 resp = NULL;
527 retval = converse(ctx->pamh, 1, pmsg, &resp);
529 if (resp) {
530 _pam_drop_reply(resp, 1);
532 return retval;
535 static int _make_remark_v(struct pwb_context *ctx,
536 int type,
537 const char *format,
538 va_list args)
540 char *var;
541 int ret;
543 ret = vasprintf(&var, format, args);
544 if (ret < 0) {
545 _pam_log(ctx, LOG_ERR, "memory allocation failure");
546 return ret;
549 ret = _make_remark(ctx, type, var);
550 SAFE_FREE(var);
551 return ret;
554 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
555 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...)
557 int ret;
558 va_list args;
560 va_start(args, format);
561 ret = _make_remark_v(ctx, type, format, args);
562 va_end(args);
563 return ret;
566 static int pam_winbind_request(struct pwb_context *ctx,
567 enum winbindd_cmd req_type,
568 struct winbindd_request *request,
569 struct winbindd_response *response)
571 /* Fill in request and send down pipe */
572 winbindd_init_request(request, req_type);
574 if (winbind_write_sock(request, sizeof(*request), 0, 0) == -1) {
575 _pam_log(ctx, LOG_ERR,
576 "pam_winbind_request: write to socket failed!");
577 winbind_close_sock();
578 return PAM_SERVICE_ERR;
581 /* Wait for reply */
582 if (winbindd_read_reply(response) == -1) {
583 _pam_log(ctx, LOG_ERR,
584 "pam_winbind_request: read from socket failed!");
585 winbind_close_sock();
586 return PAM_SERVICE_ERR;
589 /* We are done with the socket - close it and avoid mischeif */
590 winbind_close_sock();
592 /* Copy reply data from socket */
593 if (response->result == WINBINDD_OK) {
594 return PAM_SUCCESS;
597 /* no need to check for pam_error codes for getpwnam() */
598 switch (req_type) {
600 case WINBINDD_GETPWNAM:
601 case WINBINDD_LOOKUPNAME:
602 if (strlen(response->data.auth.nt_status_string) > 0) {
603 _pam_log(ctx, LOG_ERR,
604 "request failed, NT error was %s",
605 response->data.auth.nt_status_string);
606 } else {
607 _pam_log(ctx, LOG_ERR, "request failed");
609 return PAM_USER_UNKNOWN;
610 default:
611 break;
614 if (response->data.auth.pam_error != PAM_SUCCESS) {
615 _pam_log(ctx, LOG_ERR,
616 "request failed: %s, "
617 "PAM error was %s (%d), NT error was %s",
618 response->data.auth.error_string,
619 pam_strerror(ctx->pamh, response->data.auth.pam_error),
620 response->data.auth.pam_error,
621 response->data.auth.nt_status_string);
622 return response->data.auth.pam_error;
625 _pam_log(ctx, LOG_ERR, "request failed, but PAM error 0!");
627 return PAM_SERVICE_ERR;
630 static int pam_winbind_request_log(struct pwb_context *ctx,
631 enum winbindd_cmd req_type,
632 struct winbindd_request *request,
633 struct winbindd_response *response,
634 const char *user)
636 int retval;
638 retval = pam_winbind_request(ctx, req_type, request, response);
640 switch (retval) {
641 case PAM_AUTH_ERR:
642 /* incorrect password */
643 _pam_log(ctx, LOG_WARNING, "user '%s' denied access "
644 "(incorrect password or invalid membership)", user);
645 return retval;
646 case PAM_ACCT_EXPIRED:
647 /* account expired */
648 _pam_log(ctx, LOG_WARNING, "user '%s' account expired",
649 user);
650 return retval;
651 case PAM_AUTHTOK_EXPIRED:
652 /* password expired */
653 _pam_log(ctx, LOG_WARNING, "user '%s' password expired",
654 user);
655 return retval;
656 case PAM_NEW_AUTHTOK_REQD:
657 /* new password required */
658 _pam_log(ctx, LOG_WARNING, "user '%s' new password "
659 "required", user);
660 return retval;
661 case PAM_USER_UNKNOWN:
662 /* the user does not exist */
663 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
664 user);
665 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
666 return PAM_IGNORE;
668 return retval;
669 case PAM_SUCCESS:
670 /* Otherwise, the authentication looked good */
671 switch (req_type) {
672 case WINBINDD_INFO:
673 break;
674 case WINBINDD_PAM_AUTH:
675 _pam_log(ctx, LOG_NOTICE,
676 "user '%s' granted access", user);
677 break;
678 case WINBINDD_PAM_CHAUTHTOK:
679 _pam_log(ctx, LOG_NOTICE,
680 "user '%s' password changed", user);
681 break;
682 default:
683 _pam_log(ctx, LOG_NOTICE,
684 "user '%s' OK", user);
685 break;
688 return retval;
689 default:
690 /* we don't know anything about this return value */
691 _pam_log(ctx, LOG_ERR,
692 "internal module error (retval = %d, user = '%s')",
693 retval, user);
694 return retval;
699 * send a password expiry message if required
701 * @param pamh PAM handle
702 * @param ctrl PAM winbind options.
703 * @param next_change expected (calculated) next expiry date.
704 * @param already_expired pointer to a boolean to indicate if the password is
705 * already expired.
707 * @return boolean Returns true if message has been sent, false if not.
710 static bool _pam_send_password_expiry_message(struct pwb_context *ctx,
711 time_t next_change,
712 time_t now,
713 int warn_pwd_expire,
714 bool *already_expired)
716 int days = 0;
717 struct tm tm_now, tm_next_change;
719 if (already_expired) {
720 *already_expired = false;
723 if (next_change <= now) {
724 PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PASSWORD_EXPIRED");
725 if (already_expired) {
726 *already_expired = true;
728 return true;
731 if ((next_change < 0) ||
732 (next_change > now + warn_pwd_expire * SECONDS_PER_DAY)) {
733 return false;
736 if ((localtime_r(&now, &tm_now) == NULL) ||
737 (localtime_r(&next_change, &tm_next_change) == NULL)) {
738 return false;
741 days = (tm_next_change.tm_yday+tm_next_change.tm_year*365) -
742 (tm_now.tm_yday+tm_now.tm_year*365);
744 if (days == 0) {
745 _make_remark(ctx, PAM_TEXT_INFO,
746 "Your password expires today");
747 return true;
750 if (days > 0 && days < warn_pwd_expire) {
751 _make_remark_format(ctx, PAM_TEXT_INFO,
752 "Your password will expire in %d %s",
753 days, (days > 1) ? "days":"day");
754 return true;
757 return false;
761 * Send a warning if the password expires in the near future
763 * @param pamh PAM handle
764 * @param ctrl PAM winbind options.
765 * @param response The full authentication response structure.
766 * @param already_expired boolean, is the pwd already expired?
768 * @return void.
771 static void _pam_warn_password_expiry(struct pwb_context *ctx,
772 const struct winbindd_response *response,
773 int warn_pwd_expire,
774 bool *already_expired)
776 time_t now = time(NULL);
777 time_t next_change = 0;
779 if (already_expired) {
780 *already_expired = false;
783 /* accounts with ACB_PWNOEXP set never receive a warning */
784 if (response->data.auth.info3.acct_flags & ACB_PWNOEXP) {
785 return;
788 /* no point in sending a warning if this is a grace logon */
789 if (PAM_WB_GRACE_LOGON(response->data.auth.info3.user_flgs)) {
790 return;
793 /* check if the info3 must change timestamp has been set */
794 next_change = response->data.auth.info3.pass_must_change_time;
796 if (_pam_send_password_expiry_message(ctx, next_change, now,
797 warn_pwd_expire,
798 already_expired)) {
799 return;
802 /* now check for the global password policy */
803 /* good catch from Ralf Haferkamp: an expiry of "never" is translated
804 * to -1 */
805 if (response->data.auth.policy.expire <= 0) {
806 return;
809 next_change = response->data.auth.info3.pass_last_set_time +
810 response->data.auth.policy.expire;
812 if (_pam_send_password_expiry_message(ctx, next_change, now,
813 warn_pwd_expire,
814 already_expired)) {
815 return;
818 /* no warning sent */
821 #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0)
824 * Append a string, making sure not to overflow and to always return a
825 * NULL-terminated string.
827 * @param dest Destination string buffer (must already be NULL-terminated).
828 * @param src Source string buffer.
829 * @param dest_buffer_size Size of dest buffer in bytes.
831 * @return false if dest buffer is not big enough (no bytes copied), true on
832 * success.
835 static bool safe_append_string(char *dest,
836 const char *src,
837 int dest_buffer_size)
839 int dest_length = strlen(dest);
840 int src_length = strlen(src);
842 if (dest_length + src_length + 1 > dest_buffer_size) {
843 return false;
846 memcpy(dest + dest_length, src, src_length + 1);
847 return true;
851 * Convert a names into a SID string, appending it to a buffer.
853 * @param pamh PAM handle
854 * @param ctrl PAM winbind options.
855 * @param user User in PAM request.
856 * @param name Name to convert.
857 * @param sid_list_buffer Where to append the string sid.
858 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
860 * @return false on failure, true on success.
862 static bool winbind_name_to_sid_string(struct pwb_context *ctx,
863 const char *user,
864 const char *name,
865 char *sid_list_buffer,
866 int sid_list_buffer_size)
868 const char* sid_string;
869 struct winbindd_response sid_response;
871 /* lookup name? */
872 if (IS_SID_STRING(name)) {
873 sid_string = name;
874 } else {
875 struct winbindd_request sid_request;
877 ZERO_STRUCT(sid_request);
878 ZERO_STRUCT(sid_response);
880 _pam_log_debug(ctx, LOG_DEBUG,
881 "no sid given, looking up: %s\n", name);
883 /* fortunatly winbindd can handle non-separated names */
884 strncpy(sid_request.data.name.name, name,
885 sizeof(sid_request.data.name.name) - 1);
887 if (pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
888 &sid_request, &sid_response,
889 user)) {
890 _pam_log(ctx, LOG_INFO,
891 "could not lookup name: %s\n", name);
892 return false;
895 sid_string = sid_response.data.sid.sid;
898 if (!safe_append_string(sid_list_buffer, sid_string,
899 sid_list_buffer_size)) {
900 return false;
903 return true;
907 * Convert a list of names into a list of sids.
909 * @param pamh PAM handle
910 * @param ctrl PAM winbind options.
911 * @param user User in PAM request.
912 * @param name_list List of names or string sids, separated by commas.
913 * @param sid_list_buffer Where to put the list of string sids.
914 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
916 * @return false on failure, true on success.
918 static bool winbind_name_list_to_sid_string_list(struct pwb_context *ctx,
919 const char *user,
920 const char *name_list,
921 char *sid_list_buffer,
922 int sid_list_buffer_size)
924 bool result = false;
925 char *current_name = NULL;
926 const char *search_location;
927 const char *comma;
929 if (sid_list_buffer_size > 0) {
930 sid_list_buffer[0] = 0;
933 search_location = name_list;
934 while ((comma = strstr(search_location, ",")) != NULL) {
935 current_name = strndup(search_location,
936 comma - search_location);
937 if (NULL == current_name) {
938 goto out;
941 if (!winbind_name_to_sid_string(ctx, user,
942 current_name,
943 sid_list_buffer,
944 sid_list_buffer_size)) {
945 goto out;
948 SAFE_FREE(current_name);
950 if (!safe_append_string(sid_list_buffer, ",",
951 sid_list_buffer_size)) {
952 goto out;
955 search_location = comma + 1;
958 if (!winbind_name_to_sid_string(ctx, user, search_location,
959 sid_list_buffer,
960 sid_list_buffer_size)) {
961 goto out;
964 result = true;
966 out:
967 SAFE_FREE(current_name);
968 return result;
972 * put krb5ccname variable into environment
974 * @param pamh PAM handle
975 * @param ctrl PAM winbind options.
976 * @param krb5ccname env variable retrieved from winbindd.
978 * @return void.
981 static void _pam_setup_krb5_env(struct pwb_context *ctx,
982 const char *krb5ccname)
984 char var[PATH_MAX];
985 int ret;
987 if (off(ctx->ctrl, WINBIND_KRB5_AUTH)) {
988 return;
991 if (!krb5ccname || (strlen(krb5ccname) == 0)) {
992 return;
995 _pam_log_debug(ctx, LOG_DEBUG,
996 "request returned KRB5CCNAME: %s", krb5ccname);
998 if (snprintf(var, sizeof(var), "KRB5CCNAME=%s", krb5ccname) == -1) {
999 return;
1002 ret = pam_putenv(ctx->pamh, var);
1003 if (ret) {
1004 _pam_log(ctx, LOG_ERR,
1005 "failed to set KRB5CCNAME to %s: %s",
1006 var, pam_strerror(ctx->pamh, ret));
1011 * Set string into the PAM stack.
1013 * @param pamh PAM handle
1014 * @param ctrl PAM winbind options.
1015 * @param data_name Key name for pam_set_data.
1016 * @param value String value.
1018 * @return void.
1021 static void _pam_set_data_string(struct pwb_context *ctx,
1022 const char *data_name,
1023 const char *value)
1025 int ret;
1027 if (!data_name || !value || (strlen(data_name) == 0) ||
1028 (strlen(value) == 0)) {
1029 return;
1032 ret = pam_set_data(ctx->pamh, data_name, (void *)strdup(value),
1033 _pam_winbind_cleanup_func);
1034 if (ret) {
1035 _pam_log_debug(ctx, LOG_DEBUG,
1036 "Could not set data %s: %s\n",
1037 data_name, pam_strerror(ctx->pamh, ret));
1043 * Set info3 strings into the PAM stack.
1045 * @param pamh PAM handle
1046 * @param ctrl PAM winbind options.
1047 * @param data_name Key name for pam_set_data.
1048 * @param value String value.
1050 * @return void.
1053 static void _pam_set_data_info3(struct pwb_context *ctx,
1054 struct winbindd_response *response)
1056 _pam_set_data_string(ctx, PAM_WINBIND_HOMEDIR,
1057 response->data.auth.info3.home_dir);
1058 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSCRIPT,
1059 response->data.auth.info3.logon_script);
1060 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSERVER,
1061 response->data.auth.info3.logon_srv);
1062 _pam_set_data_string(ctx, PAM_WINBIND_PROFILEPATH,
1063 response->data.auth.info3.profile_path);
1067 * Free info3 strings in the PAM stack.
1069 * @param pamh PAM handle
1071 * @return void.
1074 static void _pam_free_data_info3(pam_handle_t *pamh)
1076 pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL);
1077 pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL);
1078 pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL);
1079 pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL);
1083 * Send PAM_ERROR_MSG for cached or grace logons.
1085 * @param pamh PAM handle
1086 * @param ctrl PAM winbind options.
1087 * @param username User in PAM request.
1088 * @param info3_user_flgs Info3 flags containing logon type bits.
1090 * @return void.
1093 static void _pam_warn_logon_type(struct pwb_context *ctx,
1094 const char *username,
1095 uint32_t info3_user_flgs)
1097 /* inform about logon type */
1098 if (PAM_WB_GRACE_LOGON(info3_user_flgs)) {
1100 _make_remark(ctx, PAM_ERROR_MSG,
1101 "Grace login. "
1102 "Please change your password as soon you're "
1103 "online again");
1104 _pam_log_debug(ctx, LOG_DEBUG,
1105 "User %s logged on using grace logon\n",
1106 username);
1108 } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) {
1110 _make_remark(ctx, PAM_ERROR_MSG,
1111 "Domain Controller unreachable, "
1112 "using cached credentials instead. "
1113 "Network resources may be unavailable");
1114 _pam_log_debug(ctx, LOG_DEBUG,
1115 "User %s logged on using cached credentials\n",
1116 username);
1121 * Send PAM_ERROR_MSG for krb5 errors.
1123 * @param pamh PAM handle
1124 * @param ctrl PAM winbind options.
1125 * @param username User in PAM request.
1126 * @param info3_user_flgs Info3 flags containing logon type bits.
1128 * @return void.
1131 static void _pam_warn_krb5_failure(struct pwb_context *ctx,
1132 const char *username,
1133 uint32_t info3_user_flgs)
1135 if (PAM_WB_KRB5_CLOCK_SKEW(info3_user_flgs)) {
1136 _make_remark(ctx, PAM_ERROR_MSG,
1137 "Failed to establish your Kerberos Ticket cache "
1138 "due time differences\n"
1139 "with the domain controller. "
1140 "Please verify the system time.\n");
1141 _pam_log_debug(ctx, LOG_DEBUG,
1142 "User %s: Clock skew when getting Krb5 TGT\n",
1143 username);
1148 * Compose Password Restriction String for a PAM_ERROR_MSG conversation.
1150 * @param response The struct winbindd_response.
1152 * @return string (caller needs to free).
1155 static char *_pam_compose_pwd_restriction_string(struct winbindd_response *response)
1157 char *str = NULL;
1158 size_t offset = 0, ret = 0, str_size = 1024;
1160 str = (char *)malloc(str_size);
1161 if (!str) {
1162 return NULL;
1165 memset(str, '\0', str_size);
1167 offset = snprintf(str, str_size, "Your password ");
1168 if (offset == -1) {
1169 goto failed;
1172 if (response->data.auth.policy.min_length_password > 0) {
1173 ret = snprintf(str+offset, str_size-offset,
1174 "must be at least %d characters; ",
1175 response->data.auth.policy.min_length_password);
1176 if (ret == -1) {
1177 goto failed;
1179 offset += ret;
1182 if (response->data.auth.policy.password_history > 0) {
1183 ret = snprintf(str+offset, str_size-offset,
1184 "cannot repeat any of your previous %d "
1185 "passwords; ",
1186 response->data.auth.policy.password_history);
1187 if (ret == -1) {
1188 goto failed;
1190 offset += ret;
1193 if (response->data.auth.policy.password_properties &
1194 DOMAIN_PASSWORD_COMPLEX) {
1195 ret = snprintf(str+offset, str_size-offset,
1196 "must contain capitals, numerals "
1197 "or punctuation; "
1198 "and cannot contain your account "
1199 "or full name; ");
1200 if (ret == -1) {
1201 goto failed;
1203 offset += ret;
1206 ret = snprintf(str+offset, str_size-offset,
1207 "Please type a different password. "
1208 "Type a password which meets these requirements in "
1209 "both text boxes.");
1210 if (ret == -1) {
1211 goto failed;
1214 return str;
1216 failed:
1217 SAFE_FREE(str);
1218 return NULL;
1221 /* talk to winbindd */
1222 static int winbind_auth_request(struct pwb_context *ctx,
1223 const char *user,
1224 const char *pass,
1225 const char *member,
1226 const char *cctype,
1227 const int warn_pwd_expire,
1228 struct winbindd_response *p_response,
1229 time_t *pwd_last_set,
1230 char **user_ret)
1232 struct winbindd_request request;
1233 struct winbindd_response response;
1234 int ret;
1235 bool already_expired = false;
1237 ZERO_STRUCT(request);
1238 ZERO_STRUCT(response);
1240 if (pwd_last_set) {
1241 *pwd_last_set = 0;
1244 strncpy(request.data.auth.user, user,
1245 sizeof(request.data.auth.user)-1);
1247 strncpy(request.data.auth.pass, pass,
1248 sizeof(request.data.auth.pass)-1);
1250 request.data.auth.krb5_cc_type[0] = '\0';
1251 request.data.auth.uid = -1;
1253 request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_GET_PWD_POLICY;
1255 /* Krb5 auth always has to go against the KDC of the user's realm */
1257 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1258 request.flags |= WBFLAG_PAM_CONTACT_TRUSTDOM;
1261 if (ctx->ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) {
1262 struct passwd *pwd = NULL;
1264 pwd = getpwnam(user);
1265 if (pwd == NULL) {
1266 return PAM_USER_UNKNOWN;
1268 request.data.auth.uid = pwd->pw_uid;
1271 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1273 _pam_log_debug(ctx, LOG_DEBUG,
1274 "enabling krb5 login flag\n");
1276 request.flags |= WBFLAG_PAM_KRB5 |
1277 WBFLAG_PAM_FALLBACK_AFTER_KRB5;
1280 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1281 _pam_log_debug(ctx, LOG_DEBUG,
1282 "enabling cached login flag\n");
1283 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1286 if (user_ret) {
1287 *user_ret = NULL;
1288 request.flags |= WBFLAG_PAM_UNIX_NAME;
1291 if (cctype != NULL) {
1292 strncpy(request.data.auth.krb5_cc_type, cctype,
1293 sizeof(request.data.auth.krb5_cc_type) - 1);
1294 _pam_log_debug(ctx, LOG_DEBUG,
1295 "enabling request for a %s krb5 ccache\n",
1296 cctype);
1299 request.data.auth.require_membership_of_sid[0] = '\0';
1301 if (member != NULL) {
1303 if (!winbind_name_list_to_sid_string_list(ctx, user,
1304 member,
1305 request.data.auth.require_membership_of_sid,
1306 sizeof(request.data.auth.require_membership_of_sid))) {
1308 _pam_log_debug(ctx, LOG_ERR,
1309 "failed to serialize membership of sid "
1310 "\"%s\"\n", member);
1311 return PAM_AUTH_ERR;
1315 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_AUTH,
1316 &request, &response, user);
1318 if (pwd_last_set) {
1319 *pwd_last_set = response.data.auth.info3.pass_last_set_time;
1322 if (p_response) {
1323 /* We want to process the response in the caller. */
1324 *p_response = response;
1325 return ret;
1328 if (ret) {
1329 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1330 "NT_STATUS_PASSWORD_EXPIRED");
1331 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1332 "NT_STATUS_PASSWORD_MUST_CHANGE");
1333 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1334 "NT_STATUS_INVALID_WORKSTATION");
1335 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1336 "NT_STATUS_INVALID_LOGON_HOURS");
1337 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1338 "NT_STATUS_ACCOUNT_EXPIRED");
1339 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1340 "NT_STATUS_ACCOUNT_DISABLED");
1341 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1342 "NT_STATUS_ACCOUNT_LOCKED_OUT");
1343 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1344 "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT");
1345 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1346 "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT");
1347 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1348 "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT");
1349 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1350 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1351 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1352 "NT_STATUS_NO_LOGON_SERVERS");
1353 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1354 "NT_STATUS_WRONG_PASSWORD");
1355 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1356 "NT_STATUS_ACCESS_DENIED");
1359 if (ret == PAM_SUCCESS) {
1361 /* warn a user if the password is about to expire soon */
1362 _pam_warn_password_expiry(ctx, &response,
1363 warn_pwd_expire,
1364 &already_expired);
1366 if (already_expired == true) {
1367 SMB_TIME_T last_set;
1368 last_set = response.data.auth.info3.pass_last_set_time;
1369 _pam_log_debug(ctx, LOG_DEBUG,
1370 "Password has expired "
1371 "(Password was last set: %lld, "
1372 "the policy says it should expire here "
1373 "%lld (now it's: %lu))\n",
1374 (long long int)last_set,
1375 (long long int)last_set +
1376 response.data.auth.policy.expire,
1377 time(NULL));
1379 return PAM_AUTHTOK_EXPIRED;
1382 /* inform about logon type */
1383 _pam_warn_logon_type(ctx, user,
1384 response.data.auth.info3.user_flgs);
1386 /* inform about krb5 failures */
1387 _pam_warn_krb5_failure(ctx, user,
1388 response.data.auth.info3.user_flgs);
1390 /* set some info3 info for other modules in the stack */
1391 _pam_set_data_info3(ctx, &response);
1393 /* put krb5ccname into env */
1394 _pam_setup_krb5_env(ctx, response.data.auth.krb5ccname);
1396 /* If winbindd returned a username, return the pointer to it
1397 * here. */
1398 if (user_ret && response.data.auth.unix_username[0]) {
1399 /* We have to trust it's a null terminated string. */
1400 *user_ret = strndup(response.data.auth.unix_username,
1401 sizeof(response.data.auth.unix_username) - 1);
1405 return ret;
1408 /* talk to winbindd */
1409 static int winbind_chauthtok_request(struct pwb_context *ctx,
1410 const char *user,
1411 const char *oldpass,
1412 const char *newpass,
1413 time_t pwd_last_set)
1415 struct winbindd_request request;
1416 struct winbindd_response response;
1417 int ret;
1419 ZERO_STRUCT(request);
1420 ZERO_STRUCT(response);
1422 if (request.data.chauthtok.user == NULL) {
1423 return -2;
1426 strncpy(request.data.chauthtok.user, user,
1427 sizeof(request.data.chauthtok.user) - 1);
1429 if (oldpass != NULL) {
1430 strncpy(request.data.chauthtok.oldpass, oldpass,
1431 sizeof(request.data.chauthtok.oldpass) - 1);
1432 } else {
1433 request.data.chauthtok.oldpass[0] = '\0';
1436 if (newpass != NULL) {
1437 strncpy(request.data.chauthtok.newpass, newpass,
1438 sizeof(request.data.chauthtok.newpass) - 1);
1439 } else {
1440 request.data.chauthtok.newpass[0] = '\0';
1443 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1444 request.flags = WBFLAG_PAM_KRB5 |
1445 WBFLAG_PAM_CONTACT_TRUSTDOM;
1448 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1449 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1452 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_CHAUTHTOK,
1453 &request, &response, user);
1455 if (ret == PAM_SUCCESS) {
1456 return ret;
1459 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1460 "NT_STATUS_BACKUP_CONTROLLER");
1461 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1462 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1463 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1464 "NT_STATUS_NO_LOGON_SERVERS");
1465 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1466 "NT_STATUS_ACCESS_DENIED");
1468 /* TODO: tell the min pwd length ? */
1469 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1470 "NT_STATUS_PWD_TOO_SHORT");
1472 /* TODO: tell the minage ? */
1473 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1474 "NT_STATUS_PWD_TOO_RECENT");
1476 /* TODO: tell the history length ? */
1477 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1478 "NT_STATUS_PWD_HISTORY_CONFLICT");
1480 if (!strcasecmp(response.data.auth.nt_status_string,
1481 "NT_STATUS_PASSWORD_RESTRICTION")) {
1483 char *pwd_restriction_string = NULL;
1484 SMB_TIME_T min_pwd_age;
1485 uint32_t reject_reason = response.data.auth.reject_reason;
1486 min_pwd_age = response.data.auth.policy.min_passwordage;
1488 /* FIXME: avoid to send multiple PAM messages after another */
1489 switch (reject_reason) {
1490 case -1:
1491 break;
1492 case SAMR_REJECT_OTHER:
1493 if ((min_pwd_age > 0) &&
1494 (pwd_last_set + min_pwd_age > time(NULL))) {
1495 PAM_WB_REMARK_DIRECT(ctx,
1496 "NT_STATUS_PWD_TOO_RECENT");
1498 break;
1499 case SAMR_REJECT_TOO_SHORT:
1500 PAM_WB_REMARK_DIRECT(ctx,
1501 "NT_STATUS_PWD_TOO_SHORT");
1502 break;
1503 case SAMR_REJECT_IN_HISTORY:
1504 PAM_WB_REMARK_DIRECT(ctx,
1505 "NT_STATUS_PWD_HISTORY_CONFLICT");
1506 break;
1507 case SAMR_REJECT_COMPLEXITY:
1508 _make_remark(ctx, PAM_ERROR_MSG,
1509 "Password does not meet "
1510 "complexity requirements");
1511 break;
1512 default:
1513 _pam_log_debug(ctx, LOG_DEBUG,
1514 "unknown password change "
1515 "reject reason: %d",
1516 reject_reason);
1517 break;
1520 pwd_restriction_string =
1521 _pam_compose_pwd_restriction_string(&response);
1522 if (pwd_restriction_string) {
1523 _make_remark(ctx, PAM_ERROR_MSG,
1524 pwd_restriction_string);
1525 SAFE_FREE(pwd_restriction_string);
1529 return ret;
1533 * Checks if a user has an account
1535 * return values:
1536 * 1 = User not found
1537 * 0 = OK
1538 * -1 = System error
1540 static int valid_user(struct pwb_context *ctx,
1541 const char *user)
1543 /* check not only if the user is available over NSS calls, also make
1544 * sure it's really a winbind user, this is important when stacking PAM
1545 * modules in the 'account' or 'password' facility. */
1547 struct passwd *pwd = NULL;
1548 struct winbindd_request request;
1549 struct winbindd_response response;
1550 int ret;
1552 ZERO_STRUCT(request);
1553 ZERO_STRUCT(response);
1555 pwd = getpwnam(user);
1556 if (pwd == NULL) {
1557 return 1;
1560 strncpy(request.data.username, user,
1561 sizeof(request.data.username) - 1);
1563 ret = pam_winbind_request_log(ctx, WINBINDD_GETPWNAM,
1564 &request, &response, user);
1566 switch (ret) {
1567 case PAM_USER_UNKNOWN:
1568 return 1;
1569 case PAM_SUCCESS:
1570 return 0;
1571 default:
1572 break;
1574 return -1;
1577 static char *_pam_delete(register char *xx)
1579 _pam_overwrite(xx);
1580 _pam_drop(xx);
1581 return NULL;
1585 * obtain a password from the user
1588 static int _winbind_read_password(struct pwb_context *ctx,
1589 unsigned int ctrl,
1590 const char *comment,
1591 const char *prompt1,
1592 const char *prompt2,
1593 const char **pass)
1595 int authtok_flag;
1596 int retval;
1597 const char *item;
1598 char *token;
1600 _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1603 * make sure nothing inappropriate gets returned
1606 *pass = token = NULL;
1609 * which authentication token are we getting?
1612 if (on(WINBIND__OLD_PASSWORD, ctrl)) {
1613 authtok_flag = PAM_OLDAUTHTOK;
1614 } else {
1615 authtok_flag = PAM_AUTHTOK;
1619 * should we obtain the password from a PAM item ?
1622 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) ||
1623 on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1624 retval = _pam_get_item(ctx->pamh, authtok_flag, &item);
1625 if (retval != PAM_SUCCESS) {
1626 /* very strange. */
1627 _pam_log(ctx, LOG_ALERT,
1628 "pam_get_item returned error "
1629 "to unix-read-password");
1630 return retval;
1631 } else if (item != NULL) { /* we have a password! */
1632 *pass = item;
1633 item = NULL;
1634 _pam_log(ctx, LOG_DEBUG,
1635 "pam_get_item returned a password");
1636 return PAM_SUCCESS;
1637 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1638 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
1639 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1640 && off(WINBIND__OLD_PASSWORD, ctrl)) {
1641 return PAM_AUTHTOK_RECOVER_ERR;
1645 * getting here implies we will have to get the password from the
1646 * user directly.
1650 struct pam_message msg[3], *pmsg[3];
1651 struct pam_response *resp;
1652 int i, replies;
1654 /* prepare to converse */
1656 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1657 pmsg[0] = &msg[0];
1658 msg[0].msg_style = PAM_TEXT_INFO;
1659 msg[0].msg = discard_const_p(char, comment);
1660 i = 1;
1661 } else {
1662 i = 0;
1665 pmsg[i] = &msg[i];
1666 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1667 msg[i++].msg = discard_const_p(char, prompt1);
1668 replies = 1;
1670 if (prompt2 != NULL) {
1671 pmsg[i] = &msg[i];
1672 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1673 msg[i++].msg = discard_const_p(char, prompt2);
1674 ++replies;
1676 /* so call the conversation expecting i responses */
1677 resp = NULL;
1678 retval = converse(ctx->pamh, i, pmsg, &resp);
1679 if (resp == NULL) {
1680 if (retval == PAM_SUCCESS) {
1681 retval = PAM_AUTHTOK_RECOVER_ERR;
1683 goto done;
1685 if (retval != PAM_SUCCESS) {
1686 _pam_drop_reply(resp, i);
1687 goto done;
1690 /* interpret the response */
1692 token = x_strdup(resp[i - replies].resp);
1693 if (!token) {
1694 _pam_log(ctx, LOG_NOTICE,
1695 "could not recover "
1696 "authentication token");
1697 retval = PAM_AUTHTOK_RECOVER_ERR;
1698 goto done;
1701 if (replies == 2) {
1702 /* verify that password entered correctly */
1703 if (!resp[i - 1].resp ||
1704 strcmp(token, resp[i - 1].resp)) {
1705 _pam_delete(token); /* mistyped */
1706 retval = PAM_AUTHTOK_RECOVER_ERR;
1707 _make_remark(ctx, PAM_ERROR_MSG,
1708 MISTYPED_PASS);
1713 * tidy up the conversation (resp_retcode) is ignored
1714 * -- what is it for anyway? AGM
1716 _pam_drop_reply(resp, i);
1719 done:
1720 if (retval != PAM_SUCCESS) {
1721 _pam_log_debug(ctx, LOG_DEBUG,
1722 "unable to obtain a password");
1723 return retval;
1725 /* 'token' is the entered password */
1727 /* we store this password as an item */
1729 retval = pam_set_item(ctx->pamh, authtok_flag, token);
1730 _pam_delete(token); /* clean it up */
1731 if (retval != PAM_SUCCESS ||
1732 (retval = _pam_get_item(ctx->pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1734 _pam_log(ctx, LOG_CRIT, "error manipulating password");
1735 return retval;
1739 *pass = item;
1740 item = NULL; /* break link to password */
1742 return PAM_SUCCESS;
1745 static const char *get_conf_item_string(struct pwb_context *ctx,
1746 const char *item,
1747 int config_flag)
1749 int i = 0;
1750 const char *parm_opt = NULL;
1752 if (!(ctx->ctrl & config_flag)) {
1753 goto out;
1756 /* let the pam opt take precedence over the pam_winbind.conf option */
1757 for (i=0; i<ctx->argc; i++) {
1759 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1760 char *p;
1762 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1763 _pam_log(ctx, LOG_INFO,
1764 "no \"=\" delimiter for \"%s\" found\n",
1765 item);
1766 goto out;
1768 _pam_log_debug(ctx, LOG_INFO,
1769 "PAM config: %s '%s'\n", item, p+1);
1770 return p + 1;
1774 if (ctx->dict) {
1775 char *key = NULL;
1777 if (!asprintf(&key, "global:%s", item)) {
1778 goto out;
1781 parm_opt = iniparser_getstr(ctx->dict, key);
1782 SAFE_FREE(key);
1784 _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n",
1785 item, parm_opt);
1787 out:
1788 return parm_opt;
1791 static int get_config_item_int(struct pwb_context *ctx,
1792 const char *item,
1793 int config_flag)
1795 int i, parm_opt = -1;
1797 if (!(ctx->ctrl & config_flag)) {
1798 goto out;
1801 /* let the pam opt take precedence over the pam_winbind.conf option */
1802 for (i = 0; i < ctx->argc; i++) {
1804 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1805 char *p;
1807 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1808 _pam_log(ctx, LOG_INFO,
1809 "no \"=\" delimiter for \"%s\" found\n",
1810 item);
1811 goto out;
1813 parm_opt = atoi(p + 1);
1814 _pam_log_debug(ctx, LOG_INFO,
1815 "PAM config: %s '%d'\n",
1816 item, parm_opt);
1817 return parm_opt;
1821 if (ctx->dict) {
1822 char *key = NULL;
1824 if (!asprintf(&key, "global:%s", item)) {
1825 goto out;
1828 parm_opt = iniparser_getint(ctx->dict, key, -1);
1829 SAFE_FREE(key);
1831 _pam_log_debug(ctx, LOG_INFO,
1832 "CONFIG file: %s '%d'\n",
1833 item, parm_opt);
1835 out:
1836 return parm_opt;
1839 static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx)
1841 return get_conf_item_string(ctx, "krb5_ccache_type",
1842 WINBIND_KRB5_CCACHE_TYPE);
1845 static const char *get_member_from_config(struct pwb_context *ctx)
1847 const char *ret = NULL;
1848 ret = get_conf_item_string(ctx, "require_membership_of",
1849 WINBIND_REQUIRED_MEMBERSHIP);
1850 if (ret) {
1851 return ret;
1853 return get_conf_item_string(ctx, "require-membership-of",
1854 WINBIND_REQUIRED_MEMBERSHIP);
1857 static int get_warn_pwd_expire_from_config(struct pwb_context *ctx)
1859 int ret;
1860 ret = get_config_item_int(ctx, "warn_pwd_expire",
1861 WINBIND_WARN_PWD_EXPIRE);
1862 /* no or broken setting */
1863 if (ret <= 0) {
1864 return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES;
1866 return ret;
1870 * Retrieve the winbind separator.
1872 * @param pamh PAM handle
1873 * @param ctrl PAM winbind options.
1875 * @return string separator character. NULL on failure.
1878 static char winbind_get_separator(struct pwb_context *ctx)
1880 struct winbindd_request request;
1881 struct winbindd_response response;
1883 ZERO_STRUCT(request);
1884 ZERO_STRUCT(response);
1886 if (pam_winbind_request_log(ctx, WINBINDD_INFO,
1887 &request, &response, NULL)) {
1888 return '\0';
1891 return response.data.info.winbind_separator;
1895 * Convert a upn to a name.
1897 * @param pamh PAM handle
1898 * @param ctrl PAM winbind options.
1899 * @param upn USer UPN to be trabslated.
1901 * @return converted name. NULL pointer on failure. Caller needs to free.
1904 static char* winbind_upn_to_username(struct pwb_context *ctx,
1905 const char *upn)
1907 struct winbindd_request req;
1908 struct winbindd_response resp;
1909 int retval;
1910 char *account_name;
1911 int account_name_len;
1912 char sep;
1914 /* This cannot work when the winbind separator = @ */
1916 sep = winbind_get_separator(ctx);
1917 if (!sep || sep == '@') {
1918 return NULL;
1921 /* Convert the UPN to a SID */
1923 ZERO_STRUCT(req);
1924 ZERO_STRUCT(resp);
1926 strncpy(req.data.name.dom_name, "",
1927 sizeof(req.data.name.dom_name) - 1);
1928 strncpy(req.data.name.name, upn,
1929 sizeof(req.data.name.name) - 1);
1930 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
1931 &req, &resp, upn);
1932 if (retval != PAM_SUCCESS) {
1933 return NULL;
1936 /* Convert the the SID back to the sAMAccountName */
1938 ZERO_STRUCT(req);
1939 strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
1940 ZERO_STRUCT(resp);
1941 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPSID,
1942 &req, &resp, upn);
1943 if (retval != PAM_SUCCESS) {
1944 return NULL;
1947 account_name_len = asprintf(&account_name, "%s\\%s",
1948 resp.data.name.dom_name,
1949 resp.data.name.name);
1951 return account_name;
1954 PAM_EXTERN
1955 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
1956 int argc, const char **argv)
1958 const char *username;
1959 const char *password;
1960 const char *member = NULL;
1961 const char *cctype = NULL;
1962 int warn_pwd_expire;
1963 int retval = PAM_AUTH_ERR;
1964 char *username_ret = NULL;
1965 char *new_authtok_required = NULL;
1966 char *real_username = NULL;
1967 struct pwb_context *ctx = NULL;
1969 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
1970 if (retval) {
1971 goto out;
1974 _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx);
1976 /* Get the username */
1977 retval = pam_get_user(pamh, &username, NULL);
1978 if ((retval != PAM_SUCCESS) || (!username)) {
1979 _pam_log_debug(ctx, LOG_DEBUG,
1980 "can not get the username");
1981 retval = PAM_SERVICE_ERR;
1982 goto out;
1986 #if defined(AIX)
1987 /* Decode the user name since AIX does not support logn user
1988 names by default. The name is encoded as _#uid. */
1990 if (username[0] == '_') {
1991 uid_t id = atoi(&username[1]);
1992 struct passwd *pw = NULL;
1994 if ((id!=0) && ((pw = getpwuid(id)) != NULL)) {
1995 real_username = strdup(pw->pw_name);
1998 #endif
2000 if (!real_username) {
2001 /* Just making a copy of the username we got from PAM */
2002 if ((real_username = strdup(username)) == NULL) {
2003 _pam_log_debug(ctx, LOG_DEBUG,
2004 "memory allocation failure when copying "
2005 "username");
2006 retval = PAM_SERVICE_ERR;
2007 goto out;
2011 /* Maybe this was a UPN */
2013 if (strchr(real_username, '@') != NULL) {
2014 char *samaccountname = NULL;
2016 samaccountname = winbind_upn_to_username(ctx,
2017 real_username);
2018 if (samaccountname) {
2019 free(real_username);
2020 real_username = samaccountname;
2024 retval = _winbind_read_password(ctx, ctx->ctrl, NULL,
2025 "Password: ", NULL,
2026 &password);
2028 if (retval != PAM_SUCCESS) {
2029 _pam_log(ctx, LOG_ERR,
2030 "Could not retrieve user's password");
2031 retval = PAM_AUTHTOK_ERR;
2032 goto out;
2035 /* Let's not give too much away in the log file */
2037 #ifdef DEBUG_PASSWORD
2038 _pam_log_debug(ctx, LOG_INFO,
2039 "Verify user '%s' with password '%s'",
2040 real_username, password);
2041 #else
2042 _pam_log_debug(ctx, LOG_INFO,
2043 "Verify user '%s'", real_username);
2044 #endif
2046 member = get_member_from_config(ctx);
2047 cctype = get_krb5_cc_type_from_config(ctx);
2048 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2050 /* Now use the username to look up password */
2051 retval = winbind_auth_request(ctx, real_username, password,
2052 member, cctype, warn_pwd_expire, NULL,
2053 NULL, &username_ret);
2055 if (retval == PAM_NEW_AUTHTOK_REQD ||
2056 retval == PAM_AUTHTOK_EXPIRED) {
2058 char *new_authtok_required_during_auth = NULL;
2060 if (!asprintf(&new_authtok_required, "%d", retval)) {
2061 retval = PAM_BUF_ERR;
2062 goto out;
2065 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2066 new_authtok_required,
2067 _pam_winbind_cleanup_func);
2069 retval = PAM_SUCCESS;
2071 if (!asprintf(&new_authtok_required_during_auth, "%d", true)) {
2072 retval = PAM_BUF_ERR;
2073 goto out;
2076 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2077 new_authtok_required_during_auth,
2078 _pam_winbind_cleanup_func);
2080 goto out;
2083 out:
2084 if (username_ret) {
2085 pam_set_item (pamh, PAM_USER, username_ret);
2086 _pam_log_debug(ctx, LOG_INFO,
2087 "Returned user was '%s'", username_ret);
2088 free(username_ret);
2091 if (real_username) {
2092 free(real_username);
2095 if (!new_authtok_required) {
2096 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
2099 if (retval != PAM_SUCCESS) {
2100 _pam_free_data_info3(pamh);
2103 _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
2105 _pam_winbind_free_context(ctx);
2107 return retval;
2110 PAM_EXTERN
2111 int pam_sm_setcred(pam_handle_t *pamh, int flags,
2112 int argc, const char **argv)
2114 int ret = PAM_SYSTEM_ERR;
2115 struct pwb_context *ctx = NULL;
2117 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2118 if (ret) {
2119 goto out;
2122 _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx);
2124 switch (flags & ~PAM_SILENT) {
2126 case PAM_DELETE_CRED:
2127 ret = pam_sm_close_session(pamh, flags, argc, argv);
2128 break;
2129 case PAM_REFRESH_CRED:
2130 _pam_log_debug(ctx, LOG_WARNING,
2131 "PAM_REFRESH_CRED not implemented");
2132 ret = PAM_SUCCESS;
2133 break;
2134 case PAM_REINITIALIZE_CRED:
2135 _pam_log_debug(ctx, LOG_WARNING,
2136 "PAM_REINITIALIZE_CRED not implemented");
2137 ret = PAM_SUCCESS;
2138 break;
2139 case PAM_ESTABLISH_CRED:
2140 _pam_log_debug(ctx, LOG_WARNING,
2141 "PAM_ESTABLISH_CRED not implemented");
2142 ret = PAM_SUCCESS;
2143 break;
2144 default:
2145 ret = PAM_SYSTEM_ERR;
2146 break;
2149 out:
2151 _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret);
2153 _pam_winbind_free_context(ctx);
2155 return ret;
2159 * Account management. We want to verify that the account exists
2160 * before returning PAM_SUCCESS
2162 PAM_EXTERN
2163 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
2164 int argc, const char **argv)
2166 const char *username;
2167 int ret = PAM_USER_UNKNOWN;
2168 void *tmp = NULL;
2169 struct pwb_context *ctx = NULL;
2171 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2172 if (ret) {
2173 goto out;
2176 _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx);
2179 /* Get the username */
2180 ret = pam_get_user(pamh, &username, NULL);
2181 if ((ret != PAM_SUCCESS) || (!username)) {
2182 _pam_log_debug(ctx, LOG_DEBUG,
2183 "can not get the username");
2184 ret = PAM_SERVICE_ERR;
2185 goto out;
2188 /* Verify the username */
2189 ret = valid_user(ctx, username);
2190 switch (ret) {
2191 case -1:
2192 /* some sort of system error. The log was already printed */
2193 ret = PAM_SERVICE_ERR;
2194 goto out;
2195 case 1:
2196 /* the user does not exist */
2197 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
2198 username);
2199 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
2200 ret = PAM_IGNORE;
2201 goto out;
2203 ret = PAM_USER_UNKNOWN;
2204 goto out;
2205 case 0:
2206 pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2207 (const void **)&tmp);
2208 if (tmp != NULL) {
2209 ret = atoi((const char *)tmp);
2210 switch (ret) {
2211 case PAM_AUTHTOK_EXPIRED:
2212 /* fall through, since new token is required in this case */
2213 case PAM_NEW_AUTHTOK_REQD:
2214 _pam_log(ctx, LOG_WARNING,
2215 "pam_sm_acct_mgmt success but %s is set",
2216 PAM_WINBIND_NEW_AUTHTOK_REQD);
2217 _pam_log(ctx, LOG_NOTICE,
2218 "user '%s' needs new password",
2219 username);
2220 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
2221 ret = PAM_NEW_AUTHTOK_REQD;
2222 goto out;
2223 default:
2224 _pam_log(ctx, LOG_WARNING,
2225 "pam_sm_acct_mgmt success");
2226 _pam_log(ctx, LOG_NOTICE,
2227 "user '%s' granted access", username);
2228 ret = PAM_SUCCESS;
2229 goto out;
2233 /* Otherwise, the authentication looked good */
2234 _pam_log(ctx, LOG_NOTICE,
2235 "user '%s' granted access", username);
2236 ret = PAM_SUCCESS;
2237 goto out;
2238 default:
2239 /* we don't know anything about this return value */
2240 _pam_log(ctx, LOG_ERR,
2241 "internal module error (ret = %d, user = '%s')",
2242 ret, username);
2243 ret = PAM_SERVICE_ERR;
2244 goto out;
2247 /* should not be reached */
2248 ret = PAM_IGNORE;
2250 out:
2252 _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret);
2254 _pam_winbind_free_context(ctx);
2256 return ret;
2259 PAM_EXTERN
2260 int pam_sm_open_session(pam_handle_t *pamh, int flags,
2261 int argc, const char **argv)
2263 int ret = PAM_SYSTEM_ERR;
2264 struct pwb_context *ctx = NULL;
2266 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2267 if (ret) {
2268 goto out;
2271 _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx);
2273 ret = PAM_SUCCESS;
2275 out:
2276 _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret);
2278 _pam_winbind_free_context(ctx);
2280 return ret;
2283 PAM_EXTERN
2284 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2285 int argc, const char **argv)
2287 int retval = PAM_SUCCESS;
2288 struct pwb_context *ctx = NULL;
2290 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2291 if (retval) {
2292 goto out;
2295 _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx);
2297 if (!(flags & PAM_DELETE_CRED)) {
2298 retval = PAM_SUCCESS;
2299 goto out;
2302 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2304 /* destroy the ccache here */
2305 struct winbindd_request request;
2306 struct winbindd_response response;
2307 const char *user;
2308 const char *ccname = NULL;
2309 struct passwd *pwd = NULL;
2311 ZERO_STRUCT(request);
2312 ZERO_STRUCT(response);
2314 retval = pam_get_user(pamh, &user, "Username: ");
2315 if (retval) {
2316 _pam_log(ctx, LOG_ERR,
2317 "could not identify user");
2318 goto out;
2321 if (user == NULL) {
2322 _pam_log(ctx, LOG_ERR,
2323 "username was NULL!");
2324 retval = PAM_USER_UNKNOWN;
2325 goto out;
2328 _pam_log_debug(ctx, LOG_DEBUG,
2329 "username [%s] obtained", user);
2331 ccname = pam_getenv(pamh, "KRB5CCNAME");
2332 if (ccname == NULL) {
2333 _pam_log_debug(ctx, LOG_DEBUG,
2334 "user has no KRB5CCNAME environment");
2337 strncpy(request.data.logoff.user, user,
2338 sizeof(request.data.logoff.user) - 1);
2340 if (ccname) {
2341 strncpy(request.data.logoff.krb5ccname, ccname,
2342 sizeof(request.data.logoff.krb5ccname) - 1);
2345 pwd = getpwnam(user);
2346 if (pwd == NULL) {
2347 retval = PAM_USER_UNKNOWN;
2348 goto out;
2350 request.data.logoff.uid = pwd->pw_uid;
2352 request.flags = WBFLAG_PAM_KRB5 |
2353 WBFLAG_PAM_CONTACT_TRUSTDOM;
2355 retval = pam_winbind_request_log(ctx,
2356 WINBINDD_PAM_LOGOFF,
2357 &request, &response, user);
2360 out:
2362 _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, retval);
2364 _pam_winbind_free_context(ctx);
2366 return retval;
2370 * evaluate whether we need to re-authenticate with kerberos after a
2371 * password change
2373 * @param pamh PAM handle
2374 * @param ctrl PAM winbind options.
2375 * @param user The username
2377 * @return boolean Returns true if required, false if not.
2380 static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx,
2381 const char *user)
2384 /* Make sure that we only do this if a) the chauthtok got initiated
2385 * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any
2386 * later password change via the "passwd" command if done by the user
2387 * itself */
2389 char *new_authtok_reqd_during_auth = NULL;
2390 struct passwd *pwd = NULL;
2392 if (!(ctx->ctrl & WINBIND_KRB5_AUTH)) {
2393 return false;
2396 _pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2397 &new_authtok_reqd_during_auth);
2398 pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2399 NULL, NULL);
2401 if (new_authtok_reqd_during_auth) {
2402 return true;
2405 pwd = getpwnam(user);
2406 if (!pwd) {
2407 return false;
2410 if (getuid() == pwd->pw_uid) {
2411 return true;
2414 return false;
2418 PAM_EXTERN
2419 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2420 int argc, const char **argv)
2422 unsigned int lctrl;
2423 int ret;
2424 bool cached_login = false;
2426 /* <DO NOT free() THESE> */
2427 const char *user;
2428 char *pass_old, *pass_new;
2429 /* </DO NOT free() THESE> */
2431 char *Announce;
2433 int retry = 0;
2434 char *username_ret = NULL;
2435 struct winbindd_response response;
2436 struct pwb_context *ctx = NULL;
2438 ZERO_STRUCT(response);
2440 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2441 if (ret) {
2442 goto out;
2445 _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx);
2447 cached_login = (ctx->ctrl & WINBIND_CACHED_LOGIN);
2449 /* clearing offline bit for auth */
2450 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2453 * First get the name of a user
2455 ret = pam_get_user(pamh, &user, "Username: ");
2456 if (ret) {
2457 _pam_log(ctx, LOG_ERR,
2458 "password - could not identify user");
2459 goto out;
2462 if (user == NULL) {
2463 _pam_log(ctx, LOG_ERR, "username was NULL!");
2464 ret = PAM_USER_UNKNOWN;
2465 goto out;
2468 _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user);
2470 /* check if this is really a user in winbindd, not only in NSS */
2471 ret = valid_user(ctx, user);
2472 switch (ret) {
2473 case 1:
2474 ret = PAM_USER_UNKNOWN;
2475 goto out;
2476 case -1:
2477 ret = PAM_SYSTEM_ERR;
2478 goto out;
2479 default:
2480 break;
2484 * obtain and verify the current password (OLDAUTHTOK) for
2485 * the user.
2488 if (flags & PAM_PRELIM_CHECK) {
2489 time_t pwdlastset_prelim = 0;
2491 /* instruct user what is happening */
2492 #define greeting "Changing password for "
2493 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2494 if (Announce == NULL) {
2495 _pam_log(ctx, LOG_CRIT,
2496 "password - out of memory");
2497 ret = PAM_BUF_ERR;
2498 goto out;
2500 (void) strcpy(Announce, greeting);
2501 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2502 #undef greeting
2504 lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD;
2505 ret = _winbind_read_password(ctx, lctrl,
2506 Announce,
2507 "(current) NT password: ",
2508 NULL,
2509 (const char **) &pass_old);
2510 if (ret != PAM_SUCCESS) {
2511 _pam_log(ctx, LOG_NOTICE,
2512 "password - (old) token not obtained");
2513 goto out;
2516 /* verify that this is the password for this user */
2518 ret = winbind_auth_request(ctx, user, pass_old,
2519 NULL, NULL, 0, &response,
2520 &pwdlastset_prelim, NULL);
2522 if (ret != PAM_ACCT_EXPIRED &&
2523 ret != PAM_AUTHTOK_EXPIRED &&
2524 ret != PAM_NEW_AUTHTOK_REQD &&
2525 ret != PAM_SUCCESS) {
2526 pass_old = NULL;
2527 goto out;
2530 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2531 (void *)pwdlastset_prelim, NULL);
2533 ret = pam_set_item(pamh, PAM_OLDAUTHTOK,
2534 (const void *) pass_old);
2535 pass_old = NULL;
2536 if (ret != PAM_SUCCESS) {
2537 _pam_log(ctx, LOG_CRIT,
2538 "failed to set PAM_OLDAUTHTOK");
2540 } else if (flags & PAM_UPDATE_AUTHTOK) {
2542 time_t pwdlastset_update = 0;
2545 * obtain the proposed password
2549 * get the old token back.
2552 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2554 if (ret != PAM_SUCCESS) {
2555 _pam_log(ctx, LOG_NOTICE,
2556 "user not authenticated");
2557 goto out;
2560 lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2562 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2563 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2565 retry = 0;
2566 ret = PAM_AUTHTOK_ERR;
2567 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2569 * use_authtok is to force the use of a previously entered
2570 * password -- needed for pluggable password strength checking
2573 ret = _winbind_read_password(ctx, lctrl,
2574 NULL,
2575 "Enter new NT password: ",
2576 "Retype new NT password: ",
2577 (const char **)&pass_new);
2579 if (ret != PAM_SUCCESS) {
2580 _pam_log_debug(ctx, LOG_ALERT,
2581 "password - "
2582 "new password not obtained");
2583 pass_old = NULL;/* tidy up */
2584 goto out;
2588 * At this point we know who the user is and what they
2589 * propose as their new password. Verify that the new
2590 * password is acceptable.
2593 if (pass_new[0] == '\0') {/* "\0" password = NULL */
2594 pass_new = NULL;
2599 * By reaching here we have approved the passwords and must now
2600 * rebuild the password database file.
2602 _pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2603 &pwdlastset_update);
2606 * if cached creds were enabled, make sure to set the
2607 * WINBIND_CACHED_LOGIN bit here in order to have winbindd
2608 * update the cached creds storage - gd
2610 if (cached_login) {
2611 ctx->ctrl |= WINBIND_CACHED_LOGIN;
2614 ret = winbind_chauthtok_request(ctx, user, pass_old,
2615 pass_new, pwdlastset_update);
2616 if (ret) {
2617 _pam_overwrite(pass_new);
2618 _pam_overwrite(pass_old);
2619 pass_old = pass_new = NULL;
2620 goto out;
2623 if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) {
2625 const char *member = NULL;
2626 const char *cctype = NULL;
2627 int warn_pwd_expire;
2629 member = get_member_from_config(ctx);
2630 cctype = get_krb5_cc_type_from_config(ctx);
2631 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2633 /* clearing offline bit for auth */
2634 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2636 ret = winbind_auth_request(ctx, user, pass_new,
2637 member, cctype, 0, &response,
2638 NULL, &username_ret);
2639 _pam_overwrite(pass_new);
2640 _pam_overwrite(pass_old);
2641 pass_old = pass_new = NULL;
2643 if (ret == PAM_SUCCESS) {
2645 /* warn a user if the password is about to
2646 * expire soon */
2647 _pam_warn_password_expiry(ctx, &response,
2648 warn_pwd_expire,
2649 NULL);
2651 /* set some info3 info for other modules in the
2652 * stack */
2653 _pam_set_data_info3(ctx, &response);
2655 /* put krb5ccname into env */
2656 _pam_setup_krb5_env(ctx,
2657 response.data.auth.krb5ccname);
2659 if (username_ret) {
2660 pam_set_item(pamh, PAM_USER,
2661 username_ret);
2662 _pam_log_debug(ctx, LOG_INFO,
2663 "Returned user was '%s'",
2664 username_ret);
2665 free(username_ret);
2669 goto out;
2671 } else {
2672 ret = PAM_SERVICE_ERR;
2675 out:
2677 /* Deal with offline errors. */
2678 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2679 "NT_STATUS_NO_LOGON_SERVERS");
2680 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2681 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2682 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2683 "NT_STATUS_ACCESS_DENIED");
2685 _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);
2687 _pam_winbind_free_context(ctx);
2689 return ret;
2692 #ifdef PAM_STATIC
2694 /* static module data */
2696 struct pam_module _pam_winbind_modstruct = {
2697 MODULE_NAME,
2698 pam_sm_authenticate,
2699 pam_sm_setcred,
2700 pam_sm_acct_mgmt,
2701 pam_sm_open_session,
2702 pam_sm_close_session,
2703 pam_sm_chauthtok
2706 #endif
2709 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
2710 * Copyright (c) Tim Potter <tpot@samba.org> 2000
2711 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2712 * Copyright (c) Guenther Deschner <gd@samba.org> 2005-2008
2713 * Copyright (c) Jan Rêkorajski 1999.
2714 * Copyright (c) Andrew G. Morgan 1996-8.
2715 * Copyright (c) Alex O. Yuriev, 1996.
2716 * Copyright (c) Cristian Gafton 1996.
2717 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
2719 * Redistribution and use in source and binary forms, with or without
2720 * modification, are permitted provided that the following conditions
2721 * are met:
2722 * 1. Redistributions of source code must retain the above copyright
2723 * notice, and the entire permission notice in its entirety,
2724 * including the disclaimer of warranties.
2725 * 2. Redistributions in binary form must reproduce the above copyright
2726 * notice, this list of conditions and the following disclaimer in the
2727 * documentation and/or other materials provided with the distribution.
2728 * 3. The name of the author may not be used to endorse or promote
2729 * products derived from this software without specific prior
2730 * written permission.
2732 * ALTERNATIVELY, this product may be distributed under the terms of
2733 * the GNU Public License, in which case the provisions of the GPL are
2734 * required INSTEAD OF the above restrictions. (This clause is
2735 * necessary due to a potential bad interaction between the GPL and
2736 * the restrictions contained in a BSD-style copyright.)
2738 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2739 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2740 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2741 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2742 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2743 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2744 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2745 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2746 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2747 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2748 * OF THE POSSIBILITY OF SUCH DAMAGE.