Add missing end tags
[Samba/bjacke.git] / source / nsswitch / pam_winbind.c
blob47e0e3cd128f04c15b39088521adb540dfbf3648
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 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_CHAUTHTOK,
1449 &request, &response, user);
1451 if (ret == PAM_SUCCESS) {
1452 return ret;
1455 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1456 "NT_STATUS_BACKUP_CONTROLLER");
1457 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1458 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1459 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1460 "NT_STATUS_NO_LOGON_SERVERS");
1461 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1462 "NT_STATUS_ACCESS_DENIED");
1464 /* TODO: tell the min pwd length ? */
1465 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1466 "NT_STATUS_PWD_TOO_SHORT");
1468 /* TODO: tell the minage ? */
1469 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1470 "NT_STATUS_PWD_TOO_RECENT");
1472 /* TODO: tell the history length ? */
1473 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1474 "NT_STATUS_PWD_HISTORY_CONFLICT");
1476 if (!strcasecmp(response.data.auth.nt_status_string,
1477 "NT_STATUS_PASSWORD_RESTRICTION")) {
1479 char *pwd_restriction_string = NULL;
1480 SMB_TIME_T min_pwd_age;
1481 uint32_t reject_reason = response.data.auth.reject_reason;
1482 min_pwd_age = response.data.auth.policy.min_passwordage;
1484 /* FIXME: avoid to send multiple PAM messages after another */
1485 switch (reject_reason) {
1486 case -1:
1487 break;
1488 case SAMR_REJECT_OTHER:
1489 if ((min_pwd_age > 0) &&
1490 (pwd_last_set + min_pwd_age > time(NULL))) {
1491 PAM_WB_REMARK_DIRECT(ctx,
1492 "NT_STATUS_PWD_TOO_RECENT");
1494 break;
1495 case SAMR_REJECT_TOO_SHORT:
1496 PAM_WB_REMARK_DIRECT(ctx,
1497 "NT_STATUS_PWD_TOO_SHORT");
1498 break;
1499 case SAMR_REJECT_IN_HISTORY:
1500 PAM_WB_REMARK_DIRECT(ctx,
1501 "NT_STATUS_PWD_HISTORY_CONFLICT");
1502 break;
1503 case SAMR_REJECT_COMPLEXITY:
1504 _make_remark(ctx, PAM_ERROR_MSG,
1505 "Password does not meet "
1506 "complexity requirements");
1507 break;
1508 default:
1509 _pam_log_debug(ctx, LOG_DEBUG,
1510 "unknown password change "
1511 "reject reason: %d",
1512 reject_reason);
1513 break;
1516 pwd_restriction_string =
1517 _pam_compose_pwd_restriction_string(&response);
1518 if (pwd_restriction_string) {
1519 _make_remark(ctx, PAM_ERROR_MSG,
1520 pwd_restriction_string);
1521 SAFE_FREE(pwd_restriction_string);
1525 return ret;
1529 * Checks if a user has an account
1531 * return values:
1532 * 1 = User not found
1533 * 0 = OK
1534 * -1 = System error
1536 static int valid_user(struct pwb_context *ctx,
1537 const char *user)
1539 /* check not only if the user is available over NSS calls, also make
1540 * sure it's really a winbind user, this is important when stacking PAM
1541 * modules in the 'account' or 'password' facility. */
1543 struct passwd *pwd = NULL;
1544 struct winbindd_request request;
1545 struct winbindd_response response;
1546 int ret;
1548 ZERO_STRUCT(request);
1549 ZERO_STRUCT(response);
1551 pwd = getpwnam(user);
1552 if (pwd == NULL) {
1553 return 1;
1556 strncpy(request.data.username, user,
1557 sizeof(request.data.username) - 1);
1559 ret = pam_winbind_request_log(ctx, WINBINDD_GETPWNAM,
1560 &request, &response, user);
1562 switch (ret) {
1563 case PAM_USER_UNKNOWN:
1564 return 1;
1565 case PAM_SUCCESS:
1566 return 0;
1567 default:
1568 break;
1570 return -1;
1573 static char *_pam_delete(register char *xx)
1575 _pam_overwrite(xx);
1576 _pam_drop(xx);
1577 return NULL;
1581 * obtain a password from the user
1584 static int _winbind_read_password(struct pwb_context *ctx,
1585 unsigned int ctrl,
1586 const char *comment,
1587 const char *prompt1,
1588 const char *prompt2,
1589 const char **pass)
1591 int authtok_flag;
1592 int retval;
1593 const char *item;
1594 char *token;
1596 _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1599 * make sure nothing inappropriate gets returned
1602 *pass = token = NULL;
1605 * which authentication token are we getting?
1608 if (on(WINBIND__OLD_PASSWORD, ctrl)) {
1609 authtok_flag = PAM_OLDAUTHTOK;
1610 } else {
1611 authtok_flag = PAM_AUTHTOK;
1615 * should we obtain the password from a PAM item ?
1618 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) ||
1619 on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1620 retval = _pam_get_item(ctx->pamh, authtok_flag, &item);
1621 if (retval != PAM_SUCCESS) {
1622 /* very strange. */
1623 _pam_log(ctx, LOG_ALERT,
1624 "pam_get_item returned error "
1625 "to unix-read-password");
1626 return retval;
1627 } else if (item != NULL) { /* we have a password! */
1628 *pass = item;
1629 item = NULL;
1630 _pam_log(ctx, LOG_DEBUG,
1631 "pam_get_item returned a password");
1632 return PAM_SUCCESS;
1633 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1634 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
1635 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1636 && off(WINBIND__OLD_PASSWORD, ctrl)) {
1637 return PAM_AUTHTOK_RECOVER_ERR;
1641 * getting here implies we will have to get the password from the
1642 * user directly.
1646 struct pam_message msg[3], *pmsg[3];
1647 struct pam_response *resp;
1648 int i, replies;
1650 /* prepare to converse */
1652 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1653 pmsg[0] = &msg[0];
1654 msg[0].msg_style = PAM_TEXT_INFO;
1655 msg[0].msg = discard_const_p(char, comment);
1656 i = 1;
1657 } else {
1658 i = 0;
1661 pmsg[i] = &msg[i];
1662 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1663 msg[i++].msg = discard_const_p(char, prompt1);
1664 replies = 1;
1666 if (prompt2 != NULL) {
1667 pmsg[i] = &msg[i];
1668 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1669 msg[i++].msg = discard_const_p(char, prompt2);
1670 ++replies;
1672 /* so call the conversation expecting i responses */
1673 resp = NULL;
1674 retval = converse(ctx->pamh, i, pmsg, &resp);
1675 if (resp == NULL) {
1676 if (retval == PAM_SUCCESS) {
1677 retval = PAM_AUTHTOK_RECOVER_ERR;
1679 goto done;
1681 if (retval != PAM_SUCCESS) {
1682 _pam_drop_reply(resp, i);
1683 goto done;
1686 /* interpret the response */
1688 token = x_strdup(resp[i - replies].resp);
1689 if (!token) {
1690 _pam_log(ctx, LOG_NOTICE,
1691 "could not recover "
1692 "authentication token");
1693 retval = PAM_AUTHTOK_RECOVER_ERR;
1694 goto done;
1697 if (replies == 2) {
1698 /* verify that password entered correctly */
1699 if (!resp[i - 1].resp ||
1700 strcmp(token, resp[i - 1].resp)) {
1701 _pam_delete(token); /* mistyped */
1702 retval = PAM_AUTHTOK_RECOVER_ERR;
1703 _make_remark(ctx, PAM_ERROR_MSG,
1704 MISTYPED_PASS);
1709 * tidy up the conversation (resp_retcode) is ignored
1710 * -- what is it for anyway? AGM
1712 _pam_drop_reply(resp, i);
1715 done:
1716 if (retval != PAM_SUCCESS) {
1717 _pam_log_debug(ctx, LOG_DEBUG,
1718 "unable to obtain a password");
1719 return retval;
1721 /* 'token' is the entered password */
1723 /* we store this password as an item */
1725 retval = pam_set_item(ctx->pamh, authtok_flag, token);
1726 _pam_delete(token); /* clean it up */
1727 if (retval != PAM_SUCCESS ||
1728 (retval = _pam_get_item(ctx->pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1730 _pam_log(ctx, LOG_CRIT, "error manipulating password");
1731 return retval;
1735 *pass = item;
1736 item = NULL; /* break link to password */
1738 return PAM_SUCCESS;
1741 static const char *get_conf_item_string(struct pwb_context *ctx,
1742 const char *item,
1743 int config_flag)
1745 int i = 0;
1746 const char *parm_opt = NULL;
1748 if (!(ctx->ctrl & config_flag)) {
1749 goto out;
1752 /* let the pam opt take precedence over the pam_winbind.conf option */
1753 for (i=0; i<ctx->argc; i++) {
1755 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1756 char *p;
1758 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1759 _pam_log(ctx, LOG_INFO,
1760 "no \"=\" delimiter for \"%s\" found\n",
1761 item);
1762 goto out;
1764 _pam_log_debug(ctx, LOG_INFO,
1765 "PAM config: %s '%s'\n", item, p+1);
1766 return p + 1;
1770 if (ctx->dict) {
1771 char *key = NULL;
1773 if (!asprintf(&key, "global:%s", item)) {
1774 goto out;
1777 parm_opt = iniparser_getstr(ctx->dict, key);
1778 SAFE_FREE(key);
1780 _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n",
1781 item, parm_opt);
1783 out:
1784 return parm_opt;
1787 static int get_config_item_int(struct pwb_context *ctx,
1788 const char *item,
1789 int config_flag)
1791 int i, parm_opt = -1;
1793 if (!(ctx->ctrl & config_flag)) {
1794 goto out;
1797 /* let the pam opt take precedence over the pam_winbind.conf option */
1798 for (i = 0; i < ctx->argc; i++) {
1800 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1801 char *p;
1803 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1804 _pam_log(ctx, LOG_INFO,
1805 "no \"=\" delimiter for \"%s\" found\n",
1806 item);
1807 goto out;
1809 parm_opt = atoi(p + 1);
1810 _pam_log_debug(ctx, LOG_INFO,
1811 "PAM config: %s '%d'\n",
1812 item, parm_opt);
1813 return parm_opt;
1817 if (ctx->dict) {
1818 char *key = NULL;
1820 if (!asprintf(&key, "global:%s", item)) {
1821 goto out;
1824 parm_opt = iniparser_getint(ctx->dict, key, -1);
1825 SAFE_FREE(key);
1827 _pam_log_debug(ctx, LOG_INFO,
1828 "CONFIG file: %s '%d'\n",
1829 item, parm_opt);
1831 out:
1832 return parm_opt;
1835 static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx)
1837 return get_conf_item_string(ctx, "krb5_ccache_type",
1838 WINBIND_KRB5_CCACHE_TYPE);
1841 static const char *get_member_from_config(struct pwb_context *ctx)
1843 const char *ret = NULL;
1844 ret = get_conf_item_string(ctx, "require_membership_of",
1845 WINBIND_REQUIRED_MEMBERSHIP);
1846 if (ret) {
1847 return ret;
1849 return get_conf_item_string(ctx, "require-membership-of",
1850 WINBIND_REQUIRED_MEMBERSHIP);
1853 static int get_warn_pwd_expire_from_config(struct pwb_context *ctx)
1855 int ret;
1856 ret = get_config_item_int(ctx, "warn_pwd_expire",
1857 WINBIND_WARN_PWD_EXPIRE);
1858 /* no or broken setting */
1859 if (ret <= 0) {
1860 return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES;
1862 return ret;
1866 * Retrieve the winbind separator.
1868 * @param pamh PAM handle
1869 * @param ctrl PAM winbind options.
1871 * @return string separator character. NULL on failure.
1874 static char winbind_get_separator(struct pwb_context *ctx)
1876 struct winbindd_request request;
1877 struct winbindd_response response;
1879 ZERO_STRUCT(request);
1880 ZERO_STRUCT(response);
1882 if (pam_winbind_request_log(ctx, WINBINDD_INFO,
1883 &request, &response, NULL)) {
1884 return '\0';
1887 return response.data.info.winbind_separator;
1891 * Convert a upn to a name.
1893 * @param pamh PAM handle
1894 * @param ctrl PAM winbind options.
1895 * @param upn USer UPN to be trabslated.
1897 * @return converted name. NULL pointer on failure. Caller needs to free.
1900 static char* winbind_upn_to_username(struct pwb_context *ctx,
1901 const char *upn)
1903 struct winbindd_request req;
1904 struct winbindd_response resp;
1905 int retval;
1906 char *account_name;
1907 int account_name_len;
1908 char sep;
1910 /* This cannot work when the winbind separator = @ */
1912 sep = winbind_get_separator(ctx);
1913 if (!sep || sep == '@') {
1914 return NULL;
1917 /* Convert the UPN to a SID */
1919 ZERO_STRUCT(req);
1920 ZERO_STRUCT(resp);
1922 strncpy(req.data.name.dom_name, "",
1923 sizeof(req.data.name.dom_name) - 1);
1924 strncpy(req.data.name.name, upn,
1925 sizeof(req.data.name.name) - 1);
1926 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
1927 &req, &resp, upn);
1928 if (retval != PAM_SUCCESS) {
1929 return NULL;
1932 /* Convert the the SID back to the sAMAccountName */
1934 ZERO_STRUCT(req);
1935 strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
1936 ZERO_STRUCT(resp);
1937 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPSID,
1938 &req, &resp, upn);
1939 if (retval != PAM_SUCCESS) {
1940 return NULL;
1943 account_name_len = asprintf(&account_name, "%s\\%s",
1944 resp.data.name.dom_name,
1945 resp.data.name.name);
1947 return account_name;
1950 PAM_EXTERN
1951 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
1952 int argc, const char **argv)
1954 const char *username;
1955 const char *password;
1956 const char *member = NULL;
1957 const char *cctype = NULL;
1958 int warn_pwd_expire;
1959 int retval = PAM_AUTH_ERR;
1960 char *username_ret = NULL;
1961 char *new_authtok_required = NULL;
1962 char *real_username = NULL;
1963 struct pwb_context *ctx = NULL;
1965 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
1966 if (retval) {
1967 goto out;
1970 _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx);
1972 /* Get the username */
1973 retval = pam_get_user(pamh, &username, NULL);
1974 if ((retval != PAM_SUCCESS) || (!username)) {
1975 _pam_log_debug(ctx, LOG_DEBUG,
1976 "can not get the username");
1977 retval = PAM_SERVICE_ERR;
1978 goto out;
1982 #if defined(AIX)
1983 /* Decode the user name since AIX does not support logn user
1984 names by default. The name is encoded as _#uid. */
1986 if (username[0] == '_') {
1987 uid_t id = atoi(&username[1]);
1988 struct passwd *pw = NULL;
1990 if ((id!=0) && ((pw = getpwuid(id)) != NULL)) {
1991 real_username = strdup(pw->pw_name);
1994 #endif
1996 if (!real_username) {
1997 /* Just making a copy of the username we got from PAM */
1998 if ((real_username = strdup(username)) == NULL) {
1999 _pam_log_debug(ctx, LOG_DEBUG,
2000 "memory allocation failure when copying "
2001 "username");
2002 retval = PAM_SERVICE_ERR;
2003 goto out;
2007 /* Maybe this was a UPN */
2009 if (strchr(real_username, '@') != NULL) {
2010 char *samaccountname = NULL;
2012 samaccountname = winbind_upn_to_username(ctx,
2013 real_username);
2014 if (samaccountname) {
2015 free(real_username);
2016 real_username = samaccountname;
2020 retval = _winbind_read_password(ctx, ctx->ctrl, NULL,
2021 "Password: ", NULL,
2022 &password);
2024 if (retval != PAM_SUCCESS) {
2025 _pam_log(ctx, LOG_ERR,
2026 "Could not retrieve user's password");
2027 retval = PAM_AUTHTOK_ERR;
2028 goto out;
2031 /* Let's not give too much away in the log file */
2033 #ifdef DEBUG_PASSWORD
2034 _pam_log_debug(ctx, LOG_INFO,
2035 "Verify user '%s' with password '%s'",
2036 real_username, password);
2037 #else
2038 _pam_log_debug(ctx, LOG_INFO,
2039 "Verify user '%s'", real_username);
2040 #endif
2042 member = get_member_from_config(ctx);
2043 cctype = get_krb5_cc_type_from_config(ctx);
2044 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2046 /* Now use the username to look up password */
2047 retval = winbind_auth_request(ctx, real_username, password,
2048 member, cctype, warn_pwd_expire, NULL,
2049 NULL, &username_ret);
2051 if (retval == PAM_NEW_AUTHTOK_REQD ||
2052 retval == PAM_AUTHTOK_EXPIRED) {
2054 char *new_authtok_required_during_auth = NULL;
2056 if (!asprintf(&new_authtok_required, "%d", retval)) {
2057 retval = PAM_BUF_ERR;
2058 goto out;
2061 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2062 new_authtok_required,
2063 _pam_winbind_cleanup_func);
2065 retval = PAM_SUCCESS;
2067 if (!asprintf(&new_authtok_required_during_auth, "%d", true)) {
2068 retval = PAM_BUF_ERR;
2069 goto out;
2072 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2073 new_authtok_required_during_auth,
2074 _pam_winbind_cleanup_func);
2076 goto out;
2079 out:
2080 if (username_ret) {
2081 pam_set_item (pamh, PAM_USER, username_ret);
2082 _pam_log_debug(ctx, LOG_INFO,
2083 "Returned user was '%s'", username_ret);
2084 free(username_ret);
2087 if (real_username) {
2088 free(real_username);
2091 if (!new_authtok_required) {
2092 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
2095 if (retval != PAM_SUCCESS) {
2096 _pam_free_data_info3(pamh);
2099 _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
2101 _pam_winbind_free_context(ctx);
2103 return retval;
2106 PAM_EXTERN
2107 int pam_sm_setcred(pam_handle_t *pamh, int flags,
2108 int argc, const char **argv)
2110 int ret = PAM_SYSTEM_ERR;
2111 struct pwb_context *ctx = NULL;
2113 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2114 if (ret) {
2115 goto out;
2118 _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx);
2120 switch (flags & ~PAM_SILENT) {
2122 case PAM_DELETE_CRED:
2123 ret = pam_sm_close_session(pamh, flags, argc, argv);
2124 break;
2125 case PAM_REFRESH_CRED:
2126 _pam_log_debug(ctx, LOG_WARNING,
2127 "PAM_REFRESH_CRED not implemented");
2128 ret = PAM_SUCCESS;
2129 break;
2130 case PAM_REINITIALIZE_CRED:
2131 _pam_log_debug(ctx, LOG_WARNING,
2132 "PAM_REINITIALIZE_CRED not implemented");
2133 ret = PAM_SUCCESS;
2134 break;
2135 case PAM_ESTABLISH_CRED:
2136 _pam_log_debug(ctx, LOG_WARNING,
2137 "PAM_ESTABLISH_CRED not implemented");
2138 ret = PAM_SUCCESS;
2139 break;
2140 default:
2141 ret = PAM_SYSTEM_ERR;
2142 break;
2145 out:
2147 _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret);
2149 _pam_winbind_free_context(ctx);
2151 return ret;
2155 * Account management. We want to verify that the account exists
2156 * before returning PAM_SUCCESS
2158 PAM_EXTERN
2159 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
2160 int argc, const char **argv)
2162 const char *username;
2163 int ret = PAM_USER_UNKNOWN;
2164 void *tmp = NULL;
2165 struct pwb_context *ctx = NULL;
2167 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2168 if (ret) {
2169 goto out;
2172 _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx);
2175 /* Get the username */
2176 ret = pam_get_user(pamh, &username, NULL);
2177 if ((ret != PAM_SUCCESS) || (!username)) {
2178 _pam_log_debug(ctx, LOG_DEBUG,
2179 "can not get the username");
2180 ret = PAM_SERVICE_ERR;
2181 goto out;
2184 /* Verify the username */
2185 ret = valid_user(ctx, username);
2186 switch (ret) {
2187 case -1:
2188 /* some sort of system error. The log was already printed */
2189 ret = PAM_SERVICE_ERR;
2190 goto out;
2191 case 1:
2192 /* the user does not exist */
2193 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
2194 username);
2195 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
2196 ret = PAM_IGNORE;
2197 goto out;
2199 ret = PAM_USER_UNKNOWN;
2200 goto out;
2201 case 0:
2202 pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2203 (const void **)&tmp);
2204 if (tmp != NULL) {
2205 ret = atoi((const char *)tmp);
2206 switch (ret) {
2207 case PAM_AUTHTOK_EXPIRED:
2208 /* fall through, since new token is required in this case */
2209 case PAM_NEW_AUTHTOK_REQD:
2210 _pam_log(ctx, LOG_WARNING,
2211 "pam_sm_acct_mgmt success but %s is set",
2212 PAM_WINBIND_NEW_AUTHTOK_REQD);
2213 _pam_log(ctx, LOG_NOTICE,
2214 "user '%s' needs new password",
2215 username);
2216 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
2217 ret = PAM_NEW_AUTHTOK_REQD;
2218 goto out;
2219 default:
2220 _pam_log(ctx, LOG_WARNING,
2221 "pam_sm_acct_mgmt success");
2222 _pam_log(ctx, LOG_NOTICE,
2223 "user '%s' granted access", username);
2224 ret = PAM_SUCCESS;
2225 goto out;
2229 /* Otherwise, the authentication looked good */
2230 _pam_log(ctx, LOG_NOTICE,
2231 "user '%s' granted access", username);
2232 ret = PAM_SUCCESS;
2233 goto out;
2234 default:
2235 /* we don't know anything about this return value */
2236 _pam_log(ctx, LOG_ERR,
2237 "internal module error (ret = %d, user = '%s')",
2238 ret, username);
2239 ret = PAM_SERVICE_ERR;
2240 goto out;
2243 /* should not be reached */
2244 ret = PAM_IGNORE;
2246 out:
2248 _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret);
2250 _pam_winbind_free_context(ctx);
2252 return ret;
2255 PAM_EXTERN
2256 int pam_sm_open_session(pam_handle_t *pamh, int flags,
2257 int argc, const char **argv)
2259 int ret = PAM_SYSTEM_ERR;
2260 struct pwb_context *ctx = NULL;
2262 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2263 if (ret) {
2264 goto out;
2267 _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx);
2269 ret = PAM_SUCCESS;
2271 out:
2272 _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret);
2274 _pam_winbind_free_context(ctx);
2276 return ret;
2279 PAM_EXTERN
2280 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2281 int argc, const char **argv)
2283 int retval = PAM_SUCCESS;
2284 struct pwb_context *ctx = NULL;
2286 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2287 if (retval) {
2288 goto out;
2291 _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx);
2293 if (!(flags & PAM_DELETE_CRED)) {
2294 retval = PAM_SUCCESS;
2295 goto out;
2298 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2300 /* destroy the ccache here */
2301 struct winbindd_request request;
2302 struct winbindd_response response;
2303 const char *user;
2304 const char *ccname = NULL;
2305 struct passwd *pwd = NULL;
2307 ZERO_STRUCT(request);
2308 ZERO_STRUCT(response);
2310 retval = pam_get_user(pamh, &user, "Username: ");
2311 if (retval) {
2312 _pam_log(ctx, LOG_ERR,
2313 "could not identify user");
2314 goto out;
2317 if (user == NULL) {
2318 _pam_log(ctx, LOG_ERR,
2319 "username was NULL!");
2320 retval = PAM_USER_UNKNOWN;
2321 goto out;
2324 _pam_log_debug(ctx, LOG_DEBUG,
2325 "username [%s] obtained", user);
2327 ccname = pam_getenv(pamh, "KRB5CCNAME");
2328 if (ccname == NULL) {
2329 _pam_log_debug(ctx, LOG_DEBUG,
2330 "user has no KRB5CCNAME environment");
2333 strncpy(request.data.logoff.user, user,
2334 sizeof(request.data.logoff.user) - 1);
2336 if (ccname) {
2337 strncpy(request.data.logoff.krb5ccname, ccname,
2338 sizeof(request.data.logoff.krb5ccname) - 1);
2341 pwd = getpwnam(user);
2342 if (pwd == NULL) {
2343 retval = PAM_USER_UNKNOWN;
2344 goto out;
2346 request.data.logoff.uid = pwd->pw_uid;
2348 request.flags = WBFLAG_PAM_KRB5 |
2349 WBFLAG_PAM_CONTACT_TRUSTDOM;
2351 retval = pam_winbind_request_log(ctx,
2352 WINBINDD_PAM_LOGOFF,
2353 &request, &response, user);
2356 out:
2358 _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, retval);
2360 _pam_winbind_free_context(ctx);
2362 return retval;
2366 * evaluate whether we need to re-authenticate with kerberos after a
2367 * password change
2369 * @param pamh PAM handle
2370 * @param ctrl PAM winbind options.
2371 * @param user The username
2373 * @return boolean Returns true if required, false if not.
2376 static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx,
2377 const char *user)
2380 /* Make sure that we only do this if a) the chauthtok got initiated
2381 * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any
2382 * later password change via the "passwd" command if done by the user
2383 * itself */
2385 char *new_authtok_reqd_during_auth = NULL;
2386 struct passwd *pwd = NULL;
2388 if (!(ctx->ctrl & WINBIND_KRB5_AUTH)) {
2389 return false;
2392 _pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2393 &new_authtok_reqd_during_auth);
2394 pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2395 NULL, NULL);
2397 if (new_authtok_reqd_during_auth) {
2398 return true;
2401 pwd = getpwnam(user);
2402 if (!pwd) {
2403 return false;
2406 if (getuid() == pwd->pw_uid) {
2407 return true;
2410 return false;
2414 PAM_EXTERN
2415 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2416 int argc, const char **argv)
2418 unsigned int lctrl;
2419 int ret;
2421 /* <DO NOT free() THESE> */
2422 const char *user;
2423 char *pass_old, *pass_new;
2424 /* </DO NOT free() THESE> */
2426 char *Announce;
2428 int retry = 0;
2429 char *username_ret = NULL;
2430 struct winbindd_response response;
2431 struct pwb_context *ctx = NULL;
2433 ZERO_STRUCT(response);
2435 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2436 if (ret) {
2437 goto out;
2440 _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx);
2442 /* clearing offline bit for the auth in the password change */
2443 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2446 * First get the name of a user
2448 ret = pam_get_user(pamh, &user, "Username: ");
2449 if (ret) {
2450 _pam_log(ctx, LOG_ERR,
2451 "password - could not identify user");
2452 goto out;
2455 if (user == NULL) {
2456 _pam_log(ctx, LOG_ERR, "username was NULL!");
2457 ret = PAM_USER_UNKNOWN;
2458 goto out;
2461 _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user);
2463 /* check if this is really a user in winbindd, not only in NSS */
2464 ret = valid_user(ctx, user);
2465 switch (ret) {
2466 case 1:
2467 ret = PAM_USER_UNKNOWN;
2468 goto out;
2469 case -1:
2470 ret = PAM_SYSTEM_ERR;
2471 goto out;
2472 default:
2473 break;
2477 * obtain and verify the current password (OLDAUTHTOK) for
2478 * the user.
2481 if (flags & PAM_PRELIM_CHECK) {
2482 time_t pwdlastset_prelim = 0;
2484 /* instruct user what is happening */
2485 #define greeting "Changing password for "
2486 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2487 if (Announce == NULL) {
2488 _pam_log(ctx, LOG_CRIT,
2489 "password - out of memory");
2490 ret = PAM_BUF_ERR;
2491 goto out;
2493 (void) strcpy(Announce, greeting);
2494 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2495 #undef greeting
2497 lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD;
2498 ret = _winbind_read_password(ctx, lctrl,
2499 Announce,
2500 "(current) NT password: ",
2501 NULL,
2502 (const char **) &pass_old);
2503 if (ret != PAM_SUCCESS) {
2504 _pam_log(ctx, LOG_NOTICE,
2505 "password - (old) token not obtained");
2506 goto out;
2509 /* verify that this is the password for this user */
2511 ret = winbind_auth_request(ctx, user, pass_old,
2512 NULL, NULL, 0, &response,
2513 &pwdlastset_prelim, NULL);
2515 if (ret != PAM_ACCT_EXPIRED &&
2516 ret != PAM_AUTHTOK_EXPIRED &&
2517 ret != PAM_NEW_AUTHTOK_REQD &&
2518 ret != PAM_SUCCESS) {
2519 pass_old = NULL;
2520 goto out;
2523 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2524 (void *)pwdlastset_prelim, NULL);
2526 ret = pam_set_item(pamh, PAM_OLDAUTHTOK,
2527 (const void *) pass_old);
2528 pass_old = NULL;
2529 if (ret != PAM_SUCCESS) {
2530 _pam_log(ctx, LOG_CRIT,
2531 "failed to set PAM_OLDAUTHTOK");
2533 } else if (flags & PAM_UPDATE_AUTHTOK) {
2535 time_t pwdlastset_update = 0;
2538 * obtain the proposed password
2542 * get the old token back.
2545 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2547 if (ret != PAM_SUCCESS) {
2548 _pam_log(ctx, LOG_NOTICE,
2549 "user not authenticated");
2550 goto out;
2553 lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2555 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2556 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2558 retry = 0;
2559 ret = PAM_AUTHTOK_ERR;
2560 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2562 * use_authtok is to force the use of a previously entered
2563 * password -- needed for pluggable password strength checking
2566 ret = _winbind_read_password(ctx, lctrl,
2567 NULL,
2568 "Enter new NT password: ",
2569 "Retype new NT password: ",
2570 (const char **)&pass_new);
2572 if (ret != PAM_SUCCESS) {
2573 _pam_log_debug(ctx, LOG_ALERT,
2574 "password - "
2575 "new password not obtained");
2576 pass_old = NULL;/* tidy up */
2577 goto out;
2581 * At this point we know who the user is and what they
2582 * propose as their new password. Verify that the new
2583 * password is acceptable.
2586 if (pass_new[0] == '\0') {/* "\0" password = NULL */
2587 pass_new = NULL;
2592 * By reaching here we have approved the passwords and must now
2593 * rebuild the password database file.
2595 _pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2596 &pwdlastset_update);
2598 ret = winbind_chauthtok_request(ctx, user, pass_old,
2599 pass_new, pwdlastset_update);
2600 if (ret) {
2601 _pam_overwrite(pass_new);
2602 _pam_overwrite(pass_old);
2603 pass_old = pass_new = NULL;
2604 goto out;
2607 if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) {
2609 const char *member = NULL;
2610 const char *cctype = NULL;
2611 int warn_pwd_expire;
2613 member = get_member_from_config(ctx);
2614 cctype = get_krb5_cc_type_from_config(ctx);
2615 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2617 ret = winbind_auth_request(ctx, user, pass_new,
2618 member, cctype, 0, &response,
2619 NULL, &username_ret);
2620 _pam_overwrite(pass_new);
2621 _pam_overwrite(pass_old);
2622 pass_old = pass_new = NULL;
2624 if (ret == PAM_SUCCESS) {
2626 /* warn a user if the password is about to
2627 * expire soon */
2628 _pam_warn_password_expiry(ctx, &response,
2629 warn_pwd_expire,
2630 NULL);
2632 /* set some info3 info for other modules in the
2633 * stack */
2634 _pam_set_data_info3(ctx, &response);
2636 /* put krb5ccname into env */
2637 _pam_setup_krb5_env(ctx,
2638 response.data.auth.krb5ccname);
2640 if (username_ret) {
2641 pam_set_item(pamh, PAM_USER,
2642 username_ret);
2643 _pam_log_debug(ctx, LOG_INFO,
2644 "Returned user was '%s'",
2645 username_ret);
2646 free(username_ret);
2650 goto out;
2652 } else {
2653 ret = PAM_SERVICE_ERR;
2656 out:
2658 /* Deal with offline errors. */
2659 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2660 "NT_STATUS_NO_LOGON_SERVERS");
2661 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2662 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2663 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2664 "NT_STATUS_ACCESS_DENIED");
2666 _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);
2668 _pam_winbind_free_context(ctx);
2670 return ret;
2673 #ifdef PAM_STATIC
2675 /* static module data */
2677 struct pam_module _pam_winbind_modstruct = {
2678 MODULE_NAME,
2679 pam_sm_authenticate,
2680 pam_sm_setcred,
2681 pam_sm_acct_mgmt,
2682 pam_sm_open_session,
2683 pam_sm_close_session,
2684 pam_sm_chauthtok
2687 #endif
2690 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
2691 * Copyright (c) Tim Potter <tpot@samba.org> 2000
2692 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2693 * Copyright (c) Guenther Deschner <gd@samba.org> 2005-2008
2694 * Copyright (c) Jan Rêkorajski 1999.
2695 * Copyright (c) Andrew G. Morgan 1996-8.
2696 * Copyright (c) Alex O. Yuriev, 1996.
2697 * Copyright (c) Cristian Gafton 1996.
2698 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
2700 * Redistribution and use in source and binary forms, with or without
2701 * modification, are permitted provided that the following conditions
2702 * are met:
2703 * 1. Redistributions of source code must retain the above copyright
2704 * notice, and the entire permission notice in its entirety,
2705 * including the disclaimer of warranties.
2706 * 2. Redistributions in binary form must reproduce the above copyright
2707 * notice, this list of conditions and the following disclaimer in the
2708 * documentation and/or other materials provided with the distribution.
2709 * 3. The name of the author may not be used to endorse or promote
2710 * products derived from this software without specific prior
2711 * written permission.
2713 * ALTERNATIVELY, this product may be distributed under the terms of
2714 * the GNU Public License, in which case the provisions of the GPL are
2715 * required INSTEAD OF the above restrictions. (This clause is
2716 * necessary due to a potential bad interaction between the GPL and
2717 * the restrictions contained in a BSD-style copyright.)
2719 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2720 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2721 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2722 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2723 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2724 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2725 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2726 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2727 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2728 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2729 * OF THE POSSIBILITY OF SUCH DAMAGE.