Fix bug #5729. Explicitly allow "-valid".
[Samba.git] / source / nsswitch / pam_winbind.c
blobc28c5d2697e403c8408ef42a6190484ecb9eea69
1 /* pam_winbind module
3 Copyright Andrew Tridgell <tridge@samba.org> 2000
4 Copyright Tim Potter <tpot@samba.org> 2000
5 Copyright Andrew Bartlett <abartlet@samba.org> 2002
6 Copyright Guenther Deschner <gd@samba.org> 2005-2008
8 largely based on pam_userdb by Cristian Gafton <gafton@redhat.com> also
9 contains large slabs of code from pam_unix by Elliot Lee
10 <sopwith@redhat.com> (see copyright below for full details)
13 #include "pam_winbind.h"
15 static const char *_pam_error_code_str(int err)
17 switch (err) {
18 case PAM_SUCCESS:
19 return "PAM_SUCCESS";
20 case PAM_OPEN_ERR:
21 return "PAM_OPEN_ERR";
22 case PAM_SYMBOL_ERR:
23 return "PAM_SYMBOL_ERR";
24 case PAM_SERVICE_ERR:
25 return "PAM_SERVICE_ERR";
26 case PAM_SYSTEM_ERR:
27 return "PAM_SYSTEM_ERR";
28 case PAM_BUF_ERR:
29 return "PAM_BUF_ERR";
30 case PAM_PERM_DENIED:
31 return "PAM_PERM_DENIED";
32 case PAM_AUTH_ERR:
33 return "PAM_AUTH_ERR";
34 case PAM_CRED_INSUFFICIENT:
35 return "PAM_CRED_INSUFFICIENT";
36 case PAM_AUTHINFO_UNAVAIL:
37 return "PAM_AUTHINFO_UNAVAIL";
38 case PAM_USER_UNKNOWN:
39 return "PAM_USER_UNKNOWN";
40 case PAM_MAXTRIES:
41 return "PAM_MAXTRIES";
42 case PAM_NEW_AUTHTOK_REQD:
43 return "PAM_NEW_AUTHTOK_REQD";
44 case PAM_ACCT_EXPIRED:
45 return "PAM_ACCT_EXPIRED";
46 case PAM_SESSION_ERR:
47 return "PAM_SESSION_ERR";
48 case PAM_CRED_UNAVAIL:
49 return "PAM_CRED_UNAVAIL";
50 case PAM_CRED_EXPIRED:
51 return "PAM_CRED_EXPIRED";
52 case PAM_CRED_ERR:
53 return "PAM_CRED_ERR";
54 case PAM_NO_MODULE_DATA:
55 return "PAM_NO_MODULE_DATA";
56 case PAM_CONV_ERR:
57 return "PAM_CONV_ERR";
58 case PAM_AUTHTOK_ERR:
59 return "PAM_AUTHTOK_ERR";
60 case PAM_AUTHTOK_RECOVERY_ERR:
61 return "PAM_AUTHTOK_RECOVERY_ERR";
62 case PAM_AUTHTOK_LOCK_BUSY:
63 return "PAM_AUTHTOK_LOCK_BUSY";
64 case PAM_AUTHTOK_DISABLE_AGING:
65 return "PAM_AUTHTOK_DISABLE_AGING";
66 case PAM_TRY_AGAIN:
67 return "PAM_TRY_AGAIN";
68 case PAM_IGNORE:
69 return "PAM_IGNORE";
70 case PAM_ABORT:
71 return "PAM_ABORT";
72 case PAM_AUTHTOK_EXPIRED:
73 return "PAM_AUTHTOK_EXPIRED";
74 case PAM_MODULE_UNKNOWN:
75 return "PAM_MODULE_UNKNOWN";
76 case PAM_BAD_ITEM:
77 return "PAM_BAD_ITEM";
78 case PAM_CONV_AGAIN:
79 return "PAM_CONV_AGAIN";
80 case PAM_INCOMPLETE:
81 return "PAM_INCOMPLETE";
82 default:
83 return NULL;
87 #define _PAM_LOG_FUNCTION_ENTER(function, ctx) \
88 do { \
89 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] ENTER: " \
90 function " (flags: 0x%04x)", ctx->pamh, ctx->flags); \
91 _pam_log_state(ctx); \
92 } while (0)
94 #define _PAM_LOG_FUNCTION_LEAVE(function, ctx, retval) \
95 do { \
96 _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] LEAVE: " \
97 function " returning %d (%s)", ctx->pamh, retval, \
98 _pam_error_code_str(retval)); \
99 _pam_log_state(ctx); \
100 } while (0)
102 /* data tokens */
104 #define MAX_PASSWD_TRIES 3
107 * Work around the pam API that has functions with void ** as parameters
108 * These lead to strict aliasing warnings with gcc.
110 static int _pam_get_item(const pam_handle_t *pamh,
111 int item_type,
112 const void *_item)
114 const void **item = (const void **)_item;
115 return pam_get_item(pamh, item_type, item);
117 static int _pam_get_data(const pam_handle_t *pamh,
118 const char *module_data_name,
119 const void *_data)
121 const void **data = (const void **)_data;
122 return pam_get_data(pamh, module_data_name, data);
125 /* some syslogging */
127 #ifdef HAVE_PAM_VSYSLOG
128 static void _pam_log_int(const pam_handle_t *pamh,
129 int err,
130 const char *format,
131 va_list args)
133 pam_vsyslog(pamh, err, format, args);
135 #else
136 static void _pam_log_int(const pam_handle_t *pamh,
137 int err,
138 const char *format,
139 va_list args)
141 char *format2 = NULL;
142 const char *service;
144 _pam_get_item(pamh, PAM_SERVICE, &service);
146 format2 = (char *)malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
147 if (format2 == NULL) {
148 /* what else todo ? */
149 vsyslog(err, format, args);
150 return;
153 sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
154 vsyslog(err, format2, args);
155 SAFE_FREE(format2);
157 #endif /* HAVE_PAM_VSYSLOG */
159 static bool _pam_log_is_silent(int ctrl)
161 return on(ctrl, WINBIND_SILENT);
164 static void _pam_log(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
165 static void _pam_log(struct pwb_context *r, int err, const char *format, ...)
167 va_list args;
169 if (_pam_log_is_silent(r->ctrl)) {
170 return;
173 va_start(args, format);
174 _pam_log_int(r->pamh, err, format, args);
175 va_end(args);
177 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
178 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
180 va_list args;
182 if (_pam_log_is_silent(ctrl)) {
183 return;
186 va_start(args, format);
187 _pam_log_int(pamh, err, format, args);
188 va_end(args);
191 static bool _pam_log_is_debug_enabled(int ctrl)
193 if (ctrl == -1) {
194 return false;
197 if (_pam_log_is_silent(ctrl)) {
198 return false;
201 if (!(ctrl & WINBIND_DEBUG_ARG)) {
202 return false;
205 return true;
208 static bool _pam_log_is_debug_state_enabled(int ctrl)
210 if (!(ctrl & WINBIND_DEBUG_STATE)) {
211 return false;
214 return _pam_log_is_debug_enabled(ctrl);
217 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
218 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...)
220 va_list args;
222 if (!_pam_log_is_debug_enabled(r->ctrl)) {
223 return;
226 va_start(args, format);
227 _pam_log_int(r->pamh, err, format, args);
228 va_end(args);
230 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
231 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
233 va_list args;
235 if (!_pam_log_is_debug_enabled(ctrl)) {
236 return;
239 va_start(args, format);
240 _pam_log_int(pamh, err, format, args);
241 va_end(args);
244 static void _pam_log_state_datum(struct pwb_context *ctx,
245 int item_type,
246 const char *key,
247 int is_string)
249 const void *data = NULL;
250 if (item_type != 0) {
251 pam_get_item(ctx->pamh, item_type, &data);
252 } else {
253 pam_get_data(ctx->pamh, key, &data);
255 if (data != NULL) {
256 const char *type = (item_type != 0) ? "ITEM" : "DATA";
257 if (is_string != 0) {
258 _pam_log_debug(ctx, LOG_DEBUG,
259 "[pamh: %p] STATE: %s(%s) = \"%s\" (%p)",
260 ctx->pamh, type, key, (const char *)data,
261 data);
262 } else {
263 _pam_log_debug(ctx, LOG_DEBUG,
264 "[pamh: %p] STATE: %s(%s) = %p",
265 ctx->pamh, type, key, data);
270 #define _PAM_LOG_STATE_DATA_POINTER(ctx, module_data_name) \
271 _pam_log_state_datum(ctx, 0, module_data_name, 0)
273 #define _PAM_LOG_STATE_DATA_STRING(ctx, module_data_name) \
274 _pam_log_state_datum(ctx, 0, module_data_name, 1)
276 #define _PAM_LOG_STATE_ITEM_POINTER(ctx, item_type) \
277 _pam_log_state_datum(ctx, item_type, #item_type, 0)
279 #define _PAM_LOG_STATE_ITEM_STRING(ctx, item_type) \
280 _pam_log_state_datum(ctx, item_type, #item_type, 1)
282 #ifdef DEBUG_PASSWORD
283 #define _LOG_PASSWORD_AS_STRING 1
284 #else
285 #define _LOG_PASSWORD_AS_STRING 0
286 #endif
288 #define _PAM_LOG_STATE_ITEM_PASSWORD(ctx, item_type) \
289 _pam_log_state_datum(ctx, item_type, #item_type, \
290 _LOG_PASSWORD_AS_STRING)
292 static void _pam_log_state(struct pwb_context *ctx)
294 if (!_pam_log_is_debug_state_enabled(ctx->ctrl)) {
295 return;
298 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_SERVICE);
299 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER);
300 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_TTY);
301 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RHOST);
302 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RUSER);
303 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_OLDAUTHTOK);
304 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_AUTHTOK);
305 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER_PROMPT);
306 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_CONV);
307 #ifdef PAM_FAIL_DELAY
308 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_FAIL_DELAY);
309 #endif
310 #ifdef PAM_REPOSITORY
311 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_REPOSITORY);
312 #endif
314 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_HOMEDIR);
315 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSCRIPT);
316 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSERVER);
317 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_PROFILEPATH);
318 _PAM_LOG_STATE_DATA_STRING(ctx,
319 PAM_WINBIND_NEW_AUTHTOK_REQD);
320 /* Use atoi to get PAM result code */
321 _PAM_LOG_STATE_DATA_STRING(ctx,
322 PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH);
323 _PAM_LOG_STATE_DATA_POINTER(ctx, PAM_WINBIND_PWD_LAST_SET);
326 static int _pam_parse(const pam_handle_t *pamh,
327 int flags,
328 int argc,
329 const char **argv,
330 dictionary **result_d)
332 int ctrl = 0;
333 const char *config_file = NULL;
334 int i;
335 const char **v;
336 dictionary *d = NULL;
338 if (flags & PAM_SILENT) {
339 ctrl |= WINBIND_SILENT;
342 for (i=argc,v=argv; i-- > 0; ++v) {
343 if (!strncasecmp(*v, "config", strlen("config"))) {
344 ctrl |= WINBIND_CONFIG_FILE;
345 config_file = v[i];
346 break;
350 if (config_file == NULL) {
351 config_file = PAM_WINBIND_CONFIG_FILE;
354 d = iniparser_load(config_file);
355 if (d == NULL) {
356 goto config_from_pam;
359 if (iniparser_getboolean(d, "global:debug", false)) {
360 ctrl |= WINBIND_DEBUG_ARG;
363 if (iniparser_getboolean(d, "global:debug_state", false)) {
364 ctrl |= WINBIND_DEBUG_STATE;
367 if (iniparser_getboolean(d, "global:cached_login", false)) {
368 ctrl |= WINBIND_CACHED_LOGIN;
371 if (iniparser_getboolean(d, "global:krb5_auth", false)) {
372 ctrl |= WINBIND_KRB5_AUTH;
375 if (iniparser_getboolean(d, "global:silent", false)) {
376 ctrl |= WINBIND_SILENT;
379 if (iniparser_getstr(d, "global:krb5_ccache_type") != NULL) {
380 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
383 if ((iniparser_getstr(d, "global:require-membership-of") != NULL) ||
384 (iniparser_getstr(d, "global:require_membership_of") != NULL)) {
385 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
388 if (iniparser_getboolean(d, "global:try_first_pass", false)) {
389 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
392 if (iniparser_getint(d, "global:warn_pwd_expire", 0)) {
393 ctrl |= WINBIND_WARN_PWD_EXPIRE;
396 config_from_pam:
397 /* step through arguments */
398 for (i=argc,v=argv; i-- > 0; ++v) {
400 /* generic options */
401 if (!strcmp(*v,"debug"))
402 ctrl |= WINBIND_DEBUG_ARG;
403 else if (!strcasecmp(*v, "debug_state"))
404 ctrl |= WINBIND_DEBUG_STATE;
405 else if (!strcasecmp(*v, "silent"))
406 ctrl |= WINBIND_SILENT;
407 else if (!strcasecmp(*v, "use_authtok"))
408 ctrl |= WINBIND_USE_AUTHTOK_ARG;
409 else if (!strcasecmp(*v, "use_first_pass"))
410 ctrl |= WINBIND_USE_FIRST_PASS_ARG;
411 else if (!strcasecmp(*v, "try_first_pass"))
412 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
413 else if (!strcasecmp(*v, "unknown_ok"))
414 ctrl |= WINBIND_UNKNOWN_OK_ARG;
415 else if (!strncasecmp(*v, "require_membership_of",
416 strlen("require_membership_of")))
417 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
418 else if (!strncasecmp(*v, "require-membership-of",
419 strlen("require-membership-of")))
420 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
421 else if (!strcasecmp(*v, "krb5_auth"))
422 ctrl |= WINBIND_KRB5_AUTH;
423 else if (!strncasecmp(*v, "krb5_ccache_type",
424 strlen("krb5_ccache_type")))
425 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
426 else if (!strcasecmp(*v, "cached_login"))
427 ctrl |= WINBIND_CACHED_LOGIN;
428 else {
429 __pam_log(pamh, ctrl, LOG_ERR,
430 "pam_parse: unknown option: %s", *v);
431 return -1;
436 if (result_d) {
437 *result_d = d;
438 } else {
439 if (d) {
440 iniparser_freedict(d);
444 return ctrl;
447 static void _pam_winbind_free_context(struct pwb_context *ctx)
449 if (ctx->dict) {
450 iniparser_freedict(ctx->dict);
453 SAFE_FREE(ctx);
456 static int _pam_winbind_init_context(pam_handle_t *pamh,
457 int flags,
458 int argc,
459 const char **argv,
460 struct pwb_context **ctx_p)
462 struct pwb_context *r = NULL;
464 r = (struct pwb_context *)malloc(sizeof(struct pwb_context));
465 if (!r) {
466 return PAM_BUF_ERR;
469 ZERO_STRUCTP(r);
471 r->pamh = pamh;
472 r->flags = flags;
473 r->argc = argc;
474 r->argv = argv;
475 r->ctrl = _pam_parse(pamh, flags, argc, argv, &r->dict);
476 if (r->ctrl == -1) {
477 _pam_winbind_free_context(r);
478 return PAM_SYSTEM_ERR;
481 *ctx_p = r;
483 return PAM_SUCCESS;
486 static void _pam_winbind_cleanup_func(pam_handle_t *pamh,
487 void *data,
488 int error_status)
490 int ctrl = _pam_parse(pamh, 0, 0, NULL, NULL);
491 if (_pam_log_is_debug_state_enabled(ctrl)) {
492 __pam_log_debug(pamh, ctrl, LOG_DEBUG,
493 "[pamh: %p] CLEAN: cleaning up PAM data %p "
494 "(error_status = %d)", pamh, data,
495 error_status);
497 SAFE_FREE(data);
501 static const struct ntstatus_errors {
502 const char *ntstatus_string;
503 const char *error_string;
504 } ntstatus_errors[] = {
505 {"NT_STATUS_OK",
506 "Success"},
507 {"NT_STATUS_BACKUP_CONTROLLER",
508 "No primary Domain Controler available"},
509 {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND",
510 "No domain controllers found"},
511 {"NT_STATUS_NO_LOGON_SERVERS",
512 "No logon servers"},
513 {"NT_STATUS_PWD_TOO_SHORT",
514 "Password too short"},
515 {"NT_STATUS_PWD_TOO_RECENT",
516 "The password of this user is too recent to change"},
517 {"NT_STATUS_PWD_HISTORY_CONFLICT",
518 "Password is already in password history"},
519 {"NT_STATUS_PASSWORD_EXPIRED",
520 "Your password has expired"},
521 {"NT_STATUS_PASSWORD_MUST_CHANGE",
522 "You need to change your password now"},
523 {"NT_STATUS_INVALID_WORKSTATION",
524 "You are not allowed to logon from this workstation"},
525 {"NT_STATUS_INVALID_LOGON_HOURS",
526 "You are not allowed to logon at this time"},
527 {"NT_STATUS_ACCOUNT_EXPIRED",
528 "Your account has expired. "
529 "Please contact your System administrator"}, /* SCNR */
530 {"NT_STATUS_ACCOUNT_DISABLED",
531 "Your account is disabled. "
532 "Please contact your System administrator"}, /* SCNR */
533 {"NT_STATUS_ACCOUNT_LOCKED_OUT",
534 "Your account has been locked. "
535 "Please contact your System administrator"}, /* SCNR */
536 {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
537 "Invalid Trust Account"},
538 {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT",
539 "Invalid Trust Account"},
540 {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT",
541 "Invalid Trust Account"},
542 {"NT_STATUS_ACCESS_DENIED",
543 "Access is denied"},
544 {NULL, NULL}
547 static const char *_get_ntstatus_error_string(const char *nt_status_string)
549 int i;
550 for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) {
551 if (!strcasecmp(ntstatus_errors[i].ntstatus_string,
552 nt_status_string)) {
553 return ntstatus_errors[i].error_string;
556 return NULL;
559 /* --- authentication management functions --- */
561 /* Attempt a conversation */
563 static int converse(const pam_handle_t *pamh,
564 int nargs,
565 struct pam_message **message,
566 struct pam_response **response)
568 int retval;
569 struct pam_conv *conv;
571 retval = _pam_get_item(pamh, PAM_CONV, &conv);
572 if (retval == PAM_SUCCESS) {
573 retval = conv->conv(nargs,
574 (const struct pam_message **)message,
575 response, conv->appdata_ptr);
578 return retval; /* propagate error status */
582 static int _make_remark(struct pwb_context *ctx,
583 int type,
584 const char *text)
586 int retval = PAM_SUCCESS;
588 struct pam_message *pmsg[1], msg[1];
589 struct pam_response *resp;
591 if (ctx->flags & WINBIND_SILENT) {
592 return PAM_SUCCESS;
595 pmsg[0] = &msg[0];
596 msg[0].msg = discard_const_p(char, text);
597 msg[0].msg_style = type;
599 resp = NULL;
600 retval = converse(ctx->pamh, 1, pmsg, &resp);
602 if (resp) {
603 _pam_drop_reply(resp, 1);
605 return retval;
608 static int _make_remark_v(struct pwb_context *ctx,
609 int type,
610 const char *format,
611 va_list args)
613 char *var;
614 int ret;
616 ret = vasprintf(&var, format, args);
617 if (ret < 0) {
618 _pam_log(ctx, LOG_ERR, "memory allocation failure");
619 return ret;
622 ret = _make_remark(ctx, type, var);
623 SAFE_FREE(var);
624 return ret;
627 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
628 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...)
630 int ret;
631 va_list args;
633 va_start(args, format);
634 ret = _make_remark_v(ctx, type, format, args);
635 va_end(args);
636 return ret;
639 static int pam_winbind_request(struct pwb_context *ctx,
640 enum winbindd_cmd req_type,
641 struct winbindd_request *request,
642 struct winbindd_response *response)
644 /* Fill in request and send down pipe */
645 winbindd_init_request(request, req_type);
647 if (winbind_write_sock(request, sizeof(*request), 0, 0) == -1) {
648 _pam_log(ctx, LOG_ERR,
649 "pam_winbind_request: write to socket failed!");
650 winbind_close_sock();
651 return PAM_SERVICE_ERR;
654 /* Wait for reply */
655 if (winbindd_read_reply(response) == -1) {
656 _pam_log(ctx, LOG_ERR,
657 "pam_winbind_request: read from socket failed!");
658 winbind_close_sock();
659 return PAM_SERVICE_ERR;
662 /* We are done with the socket - close it and avoid mischeif */
663 winbind_close_sock();
665 /* Copy reply data from socket */
666 if (response->result == WINBINDD_OK) {
667 return PAM_SUCCESS;
670 /* no need to check for pam_error codes for getpwnam() */
671 switch (req_type) {
673 case WINBINDD_GETPWNAM:
674 case WINBINDD_LOOKUPNAME:
675 if (strlen(response->data.auth.nt_status_string) > 0) {
676 _pam_log(ctx, LOG_ERR,
677 "request failed, NT error was %s",
678 response->data.auth.nt_status_string);
679 } else {
680 _pam_log(ctx, LOG_ERR, "request failed");
682 return PAM_USER_UNKNOWN;
683 default:
684 break;
687 if (response->data.auth.pam_error != PAM_SUCCESS) {
688 _pam_log(ctx, LOG_ERR,
689 "request failed: %s, "
690 "PAM error was %s (%d), NT error was %s",
691 response->data.auth.error_string,
692 pam_strerror(ctx->pamh, response->data.auth.pam_error),
693 response->data.auth.pam_error,
694 response->data.auth.nt_status_string);
695 return response->data.auth.pam_error;
698 _pam_log(ctx, LOG_ERR, "request failed, but PAM error 0!");
700 return PAM_SERVICE_ERR;
703 static int pam_winbind_request_log(struct pwb_context *ctx,
704 enum winbindd_cmd req_type,
705 struct winbindd_request *request,
706 struct winbindd_response *response,
707 const char *user)
709 int retval;
711 retval = pam_winbind_request(ctx, req_type, request, response);
713 switch (retval) {
714 case PAM_AUTH_ERR:
715 /* incorrect password */
716 _pam_log(ctx, LOG_WARNING, "user '%s' denied access "
717 "(incorrect password or invalid membership)", user);
718 return retval;
719 case PAM_ACCT_EXPIRED:
720 /* account expired */
721 _pam_log(ctx, LOG_WARNING, "user '%s' account expired",
722 user);
723 return retval;
724 case PAM_AUTHTOK_EXPIRED:
725 /* password expired */
726 _pam_log(ctx, LOG_WARNING, "user '%s' password expired",
727 user);
728 return retval;
729 case PAM_NEW_AUTHTOK_REQD:
730 /* new password required */
731 _pam_log(ctx, LOG_WARNING, "user '%s' new password "
732 "required", user);
733 return retval;
734 case PAM_USER_UNKNOWN:
735 /* the user does not exist */
736 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
737 user);
738 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
739 return PAM_IGNORE;
741 return retval;
742 case PAM_SUCCESS:
743 /* Otherwise, the authentication looked good */
744 switch (req_type) {
745 case WINBINDD_INFO:
746 break;
747 case WINBINDD_PAM_AUTH:
748 _pam_log(ctx, LOG_NOTICE,
749 "user '%s' granted access", user);
750 break;
751 case WINBINDD_PAM_CHAUTHTOK:
752 _pam_log(ctx, LOG_NOTICE,
753 "user '%s' password changed", user);
754 break;
755 default:
756 _pam_log(ctx, LOG_NOTICE,
757 "user '%s' OK", user);
758 break;
761 return retval;
762 default:
763 /* we don't know anything about this return value */
764 _pam_log(ctx, LOG_ERR,
765 "internal module error (retval = %d, user = '%s')",
766 retval, user);
767 return retval;
772 * send a password expiry message if required
774 * @param ctx PAM winbind context.
775 * @param next_change expected (calculated) next expiry date.
776 * @param already_expired pointer to a boolean to indicate if the password is
777 * already expired.
779 * @return boolean Returns true if message has been sent, false if not.
782 static bool _pam_send_password_expiry_message(struct pwb_context *ctx,
783 time_t next_change,
784 time_t now,
785 int warn_pwd_expire,
786 bool *already_expired)
788 int days = 0;
789 struct tm tm_now, tm_next_change;
791 if (already_expired) {
792 *already_expired = false;
795 if (next_change <= now) {
796 PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PASSWORD_EXPIRED");
797 if (already_expired) {
798 *already_expired = true;
800 return true;
803 if ((next_change < 0) ||
804 (next_change > now + warn_pwd_expire * SECONDS_PER_DAY)) {
805 return false;
808 if ((localtime_r(&now, &tm_now) == NULL) ||
809 (localtime_r(&next_change, &tm_next_change) == NULL)) {
810 return false;
813 days = (tm_next_change.tm_yday+tm_next_change.tm_year*365) -
814 (tm_now.tm_yday+tm_now.tm_year*365);
816 if (days == 0) {
817 _make_remark(ctx, PAM_TEXT_INFO,
818 "Your password expires today");
819 return true;
822 if (days > 0 && days < warn_pwd_expire) {
823 _make_remark_format(ctx, PAM_TEXT_INFO,
824 "Your password will expire in %d %s",
825 days, (days > 1) ? "days":"day");
826 return true;
829 return false;
833 * Send a warning if the password expires in the near future
835 * @param ctx PAM winbind context.
836 * @param response The full authentication response structure.
837 * @param already_expired boolean, is the pwd already expired?
839 * @return void.
842 static void _pam_warn_password_expiry(struct pwb_context *ctx,
843 const struct winbindd_response *response,
844 int warn_pwd_expire,
845 bool *already_expired)
847 time_t now = time(NULL);
848 time_t next_change = 0;
850 if (already_expired) {
851 *already_expired = false;
854 /* accounts with ACB_PWNOEXP set never receive a warning */
855 if (response->data.auth.info3.acct_flags & ACB_PWNOEXP) {
856 return;
859 /* no point in sending a warning if this is a grace logon */
860 if (PAM_WB_GRACE_LOGON(response->data.auth.info3.user_flgs)) {
861 return;
864 /* check if the info3 must change timestamp has been set */
865 next_change = response->data.auth.info3.pass_must_change_time;
867 if (_pam_send_password_expiry_message(ctx, next_change, now,
868 warn_pwd_expire,
869 already_expired)) {
870 return;
873 /* now check for the global password policy */
874 /* good catch from Ralf Haferkamp: an expiry of "never" is translated
875 * to -1 */
876 if (response->data.auth.policy.expire <= 0) {
877 return;
880 next_change = response->data.auth.info3.pass_last_set_time +
881 response->data.auth.policy.expire;
883 if (_pam_send_password_expiry_message(ctx, next_change, now,
884 warn_pwd_expire,
885 already_expired)) {
886 return;
889 /* no warning sent */
892 #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0)
895 * Append a string, making sure not to overflow and to always return a
896 * NULL-terminated string.
898 * @param dest Destination string buffer (must already be NULL-terminated).
899 * @param src Source string buffer.
900 * @param dest_buffer_size Size of dest buffer in bytes.
902 * @return false if dest buffer is not big enough (no bytes copied), true on
903 * success.
906 static bool safe_append_string(char *dest,
907 const char *src,
908 int dest_buffer_size)
910 int dest_length = strlen(dest);
911 int src_length = strlen(src);
913 if (dest_length + src_length + 1 > dest_buffer_size) {
914 return false;
917 memcpy(dest + dest_length, src, src_length + 1);
918 return true;
922 * Convert a names into a SID string, appending it to a buffer.
924 * @param ctx PAM winbind context.
925 * @param user User in PAM request.
926 * @param name Name to convert.
927 * @param sid_list_buffer Where to append the string sid.
928 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
930 * @return false on failure, true on success.
932 static bool winbind_name_to_sid_string(struct pwb_context *ctx,
933 const char *user,
934 const char *name,
935 char *sid_list_buffer,
936 int sid_list_buffer_size)
938 const char* sid_string;
939 struct winbindd_response sid_response;
941 /* lookup name? */
942 if (IS_SID_STRING(name)) {
943 sid_string = name;
944 } else {
945 struct winbindd_request sid_request;
947 ZERO_STRUCT(sid_request);
948 ZERO_STRUCT(sid_response);
950 _pam_log_debug(ctx, LOG_DEBUG,
951 "no sid given, looking up: %s\n", name);
953 /* fortunatly winbindd can handle non-separated names */
954 strncpy(sid_request.data.name.name, name,
955 sizeof(sid_request.data.name.name) - 1);
957 if (pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
958 &sid_request, &sid_response,
959 user)) {
960 _pam_log(ctx, LOG_INFO,
961 "could not lookup name: %s\n", name);
962 return false;
965 sid_string = sid_response.data.sid.sid;
968 if (!safe_append_string(sid_list_buffer, sid_string,
969 sid_list_buffer_size)) {
970 return false;
973 return true;
977 * Convert a list of names into a list of sids.
979 * @param ctx PAM winbind context.
980 * @param user User in PAM request.
981 * @param name_list List of names or string sids, separated by commas.
982 * @param sid_list_buffer Where to put the list of string sids.
983 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
985 * @return false on failure, true on success.
987 static bool winbind_name_list_to_sid_string_list(struct pwb_context *ctx,
988 const char *user,
989 const char *name_list,
990 char *sid_list_buffer,
991 int sid_list_buffer_size)
993 bool result = false;
994 char *current_name = NULL;
995 const char *search_location;
996 const char *comma;
998 if (sid_list_buffer_size > 0) {
999 sid_list_buffer[0] = 0;
1002 search_location = name_list;
1003 while ((comma = strstr(search_location, ",")) != NULL) {
1004 current_name = strndup(search_location,
1005 comma - search_location);
1006 if (NULL == current_name) {
1007 goto out;
1010 if (!winbind_name_to_sid_string(ctx, user,
1011 current_name,
1012 sid_list_buffer,
1013 sid_list_buffer_size)) {
1014 goto out;
1017 SAFE_FREE(current_name);
1019 if (!safe_append_string(sid_list_buffer, ",",
1020 sid_list_buffer_size)) {
1021 goto out;
1024 search_location = comma + 1;
1027 if (!winbind_name_to_sid_string(ctx, user, search_location,
1028 sid_list_buffer,
1029 sid_list_buffer_size)) {
1030 goto out;
1033 result = true;
1035 out:
1036 SAFE_FREE(current_name);
1037 return result;
1041 * put krb5ccname variable into environment
1043 * @param ctx PAM winbind context.
1044 * @param krb5ccname env variable retrieved from winbindd.
1046 * @return void.
1049 static void _pam_setup_krb5_env(struct pwb_context *ctx,
1050 const char *krb5ccname)
1052 char var[PATH_MAX];
1053 int ret;
1055 if (off(ctx->ctrl, WINBIND_KRB5_AUTH)) {
1056 return;
1059 if (!krb5ccname || (strlen(krb5ccname) == 0)) {
1060 return;
1063 _pam_log_debug(ctx, LOG_DEBUG,
1064 "request returned KRB5CCNAME: %s", krb5ccname);
1066 if (snprintf(var, sizeof(var), "KRB5CCNAME=%s", krb5ccname) == -1) {
1067 return;
1070 ret = pam_putenv(ctx->pamh, var);
1071 if (ret) {
1072 _pam_log(ctx, LOG_ERR,
1073 "failed to set KRB5CCNAME to %s: %s",
1074 var, pam_strerror(ctx->pamh, ret));
1079 * Set string into the PAM stack.
1081 * @param ctx PAM winbind context.
1082 * @param data_name Key name for pam_set_data.
1083 * @param value String value.
1085 * @return void.
1088 static void _pam_set_data_string(struct pwb_context *ctx,
1089 const char *data_name,
1090 const char *value)
1092 int ret;
1094 if (!data_name || !value || (strlen(data_name) == 0) ||
1095 (strlen(value) == 0)) {
1096 return;
1099 ret = pam_set_data(ctx->pamh, data_name, (void *)strdup(value),
1100 _pam_winbind_cleanup_func);
1101 if (ret) {
1102 _pam_log_debug(ctx, LOG_DEBUG,
1103 "Could not set data %s: %s\n",
1104 data_name, pam_strerror(ctx->pamh, ret));
1110 * Set info3 strings into the PAM stack.
1112 * @param ctx PAM winbind context.
1113 * @param data_name Key name for pam_set_data.
1114 * @param value String value.
1116 * @return void.
1119 static void _pam_set_data_info3(struct pwb_context *ctx,
1120 struct winbindd_response *response)
1122 _pam_set_data_string(ctx, PAM_WINBIND_HOMEDIR,
1123 response->data.auth.info3.home_dir);
1124 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSCRIPT,
1125 response->data.auth.info3.logon_script);
1126 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSERVER,
1127 response->data.auth.info3.logon_srv);
1128 _pam_set_data_string(ctx, PAM_WINBIND_PROFILEPATH,
1129 response->data.auth.info3.profile_path);
1133 * Free info3 strings in the PAM stack.
1135 * @param pamh PAM handle
1137 * @return void.
1140 static void _pam_free_data_info3(pam_handle_t *pamh)
1142 pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL);
1143 pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL);
1144 pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL);
1145 pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL);
1149 * Send PAM_ERROR_MSG for cached or grace logons.
1151 * @param ctx PAM winbind context.
1152 * @param username User in PAM request.
1153 * @param info3_user_flgs Info3 flags containing logon type bits.
1155 * @return void.
1158 static void _pam_warn_logon_type(struct pwb_context *ctx,
1159 const char *username,
1160 uint32_t info3_user_flgs)
1162 /* inform about logon type */
1163 if (PAM_WB_GRACE_LOGON(info3_user_flgs)) {
1165 _make_remark(ctx, PAM_ERROR_MSG,
1166 "Grace login. "
1167 "Please change your password as soon you're "
1168 "online again");
1169 _pam_log_debug(ctx, LOG_DEBUG,
1170 "User %s logged on using grace logon\n",
1171 username);
1173 } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) {
1175 _make_remark(ctx, PAM_ERROR_MSG,
1176 "Domain Controller unreachable, "
1177 "using cached credentials instead. "
1178 "Network resources may be unavailable");
1179 _pam_log_debug(ctx, LOG_DEBUG,
1180 "User %s logged on using cached credentials\n",
1181 username);
1186 * Send PAM_ERROR_MSG for krb5 errors.
1188 * @param ctx PAM winbind context.
1189 * @param username User in PAM request.
1190 * @param info3_user_flgs Info3 flags containing logon type bits.
1192 * @return void.
1195 static void _pam_warn_krb5_failure(struct pwb_context *ctx,
1196 const char *username,
1197 uint32_t info3_user_flgs)
1199 if (PAM_WB_KRB5_CLOCK_SKEW(info3_user_flgs)) {
1200 _make_remark(ctx, PAM_ERROR_MSG,
1201 "Failed to establish your Kerberos Ticket cache "
1202 "due time differences\n"
1203 "with the domain controller. "
1204 "Please verify the system time.\n");
1205 _pam_log_debug(ctx, LOG_DEBUG,
1206 "User %s: Clock skew when getting Krb5 TGT\n",
1207 username);
1212 * Compose Password Restriction String for a PAM_ERROR_MSG conversation.
1214 * @param response The struct winbindd_response.
1216 * @return string (caller needs to free).
1219 static char *_pam_compose_pwd_restriction_string(struct winbindd_response *response)
1221 char *str = NULL;
1222 size_t offset = 0, ret = 0, str_size = 1024;
1224 str = (char *)malloc(str_size);
1225 if (!str) {
1226 return NULL;
1229 memset(str, '\0', str_size);
1231 offset = snprintf(str, str_size, "Your password ");
1232 if (offset == -1) {
1233 goto failed;
1236 if (response->data.auth.policy.min_length_password > 0) {
1237 ret = snprintf(str+offset, str_size-offset,
1238 "must be at least %d characters; ",
1239 response->data.auth.policy.min_length_password);
1240 if (ret == -1) {
1241 goto failed;
1243 offset += ret;
1246 if (response->data.auth.policy.password_history > 0) {
1247 ret = snprintf(str+offset, str_size-offset,
1248 "cannot repeat any of your previous %d "
1249 "passwords; ",
1250 response->data.auth.policy.password_history);
1251 if (ret == -1) {
1252 goto failed;
1254 offset += ret;
1257 if (response->data.auth.policy.password_properties &
1258 DOMAIN_PASSWORD_COMPLEX) {
1259 ret = snprintf(str+offset, str_size-offset,
1260 "must contain capitals, numerals "
1261 "or punctuation; "
1262 "and cannot contain your account "
1263 "or full name; ");
1264 if (ret == -1) {
1265 goto failed;
1267 offset += ret;
1270 ret = snprintf(str+offset, str_size-offset,
1271 "Please type a different password. "
1272 "Type a password which meets these requirements in "
1273 "both text boxes.");
1274 if (ret == -1) {
1275 goto failed;
1278 return str;
1280 failed:
1281 SAFE_FREE(str);
1282 return NULL;
1285 /* talk to winbindd */
1286 static int winbind_auth_request(struct pwb_context *ctx,
1287 const char *user,
1288 const char *pass,
1289 const char *member,
1290 const char *cctype,
1291 const int warn_pwd_expire,
1292 struct winbindd_response *p_response,
1293 time_t *pwd_last_set,
1294 char **user_ret)
1296 struct winbindd_request request;
1297 struct winbindd_response response;
1298 int ret;
1299 bool already_expired = false;
1301 ZERO_STRUCT(request);
1302 ZERO_STRUCT(response);
1304 if (pwd_last_set) {
1305 *pwd_last_set = 0;
1308 strncpy(request.data.auth.user, user,
1309 sizeof(request.data.auth.user)-1);
1311 strncpy(request.data.auth.pass, pass,
1312 sizeof(request.data.auth.pass)-1);
1314 request.data.auth.krb5_cc_type[0] = '\0';
1315 request.data.auth.uid = -1;
1317 request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_GET_PWD_POLICY;
1319 /* Krb5 auth always has to go against the KDC of the user's realm */
1321 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1322 request.flags |= WBFLAG_PAM_CONTACT_TRUSTDOM;
1325 if (ctx->ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) {
1326 struct passwd *pwd = NULL;
1328 pwd = getpwnam(user);
1329 if (pwd == NULL) {
1330 return PAM_USER_UNKNOWN;
1332 request.data.auth.uid = pwd->pw_uid;
1335 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1337 _pam_log_debug(ctx, LOG_DEBUG,
1338 "enabling krb5 login flag\n");
1340 request.flags |= WBFLAG_PAM_KRB5 |
1341 WBFLAG_PAM_FALLBACK_AFTER_KRB5;
1344 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1345 _pam_log_debug(ctx, LOG_DEBUG,
1346 "enabling cached login flag\n");
1347 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1350 if (user_ret) {
1351 *user_ret = NULL;
1352 request.flags |= WBFLAG_PAM_UNIX_NAME;
1355 if (cctype != NULL) {
1356 strncpy(request.data.auth.krb5_cc_type, cctype,
1357 sizeof(request.data.auth.krb5_cc_type) - 1);
1358 _pam_log_debug(ctx, LOG_DEBUG,
1359 "enabling request for a %s krb5 ccache\n",
1360 cctype);
1363 request.data.auth.require_membership_of_sid[0] = '\0';
1365 if (member != NULL) {
1367 if (!winbind_name_list_to_sid_string_list(ctx, user,
1368 member,
1369 request.data.auth.require_membership_of_sid,
1370 sizeof(request.data.auth.require_membership_of_sid))) {
1372 _pam_log_debug(ctx, LOG_ERR,
1373 "failed to serialize membership of sid "
1374 "\"%s\"\n", member);
1375 return PAM_AUTH_ERR;
1379 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_AUTH,
1380 &request, &response, user);
1382 if (pwd_last_set) {
1383 *pwd_last_set = response.data.auth.info3.pass_last_set_time;
1386 if (p_response) {
1387 /* We want to process the response in the caller. */
1388 *p_response = response;
1389 return ret;
1392 if (ret) {
1393 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1394 "NT_STATUS_PASSWORD_EXPIRED");
1395 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1396 "NT_STATUS_PASSWORD_MUST_CHANGE");
1397 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1398 "NT_STATUS_INVALID_WORKSTATION");
1399 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1400 "NT_STATUS_INVALID_LOGON_HOURS");
1401 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1402 "NT_STATUS_ACCOUNT_EXPIRED");
1403 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1404 "NT_STATUS_ACCOUNT_DISABLED");
1405 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1406 "NT_STATUS_ACCOUNT_LOCKED_OUT");
1407 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1408 "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT");
1409 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1410 "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT");
1411 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1412 "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT");
1413 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1414 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1415 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1416 "NT_STATUS_NO_LOGON_SERVERS");
1417 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1418 "NT_STATUS_WRONG_PASSWORD");
1419 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1420 "NT_STATUS_ACCESS_DENIED");
1423 if (ret == PAM_SUCCESS) {
1425 /* warn a user if the password is about to expire soon */
1426 _pam_warn_password_expiry(ctx, &response,
1427 warn_pwd_expire,
1428 &already_expired);
1430 if (already_expired == true) {
1431 SMB_TIME_T last_set;
1432 last_set = response.data.auth.info3.pass_last_set_time;
1433 _pam_log_debug(ctx, LOG_DEBUG,
1434 "Password has expired "
1435 "(Password was last set: %lld, "
1436 "the policy says it should expire here "
1437 "%lld (now it's: %lu))\n",
1438 (long long int)last_set,
1439 (long long int)last_set +
1440 response.data.auth.policy.expire,
1441 time(NULL));
1443 return PAM_AUTHTOK_EXPIRED;
1446 /* inform about logon type */
1447 _pam_warn_logon_type(ctx, user,
1448 response.data.auth.info3.user_flgs);
1450 /* inform about krb5 failures */
1451 _pam_warn_krb5_failure(ctx, user,
1452 response.data.auth.info3.user_flgs);
1454 /* set some info3 info for other modules in the stack */
1455 _pam_set_data_info3(ctx, &response);
1457 /* put krb5ccname into env */
1458 _pam_setup_krb5_env(ctx, response.data.auth.krb5ccname);
1460 /* If winbindd returned a username, return the pointer to it
1461 * here. */
1462 if (user_ret && response.data.auth.unix_username[0]) {
1463 /* We have to trust it's a null terminated string. */
1464 *user_ret = strndup(response.data.auth.unix_username,
1465 sizeof(response.data.auth.unix_username) - 1);
1469 return ret;
1472 /* talk to winbindd */
1473 static int winbind_chauthtok_request(struct pwb_context *ctx,
1474 const char *user,
1475 const char *oldpass,
1476 const char *newpass,
1477 time_t pwd_last_set)
1479 struct winbindd_request request;
1480 struct winbindd_response response;
1481 int ret;
1483 ZERO_STRUCT(request);
1484 ZERO_STRUCT(response);
1486 if (request.data.chauthtok.user == NULL) {
1487 return -2;
1490 strncpy(request.data.chauthtok.user, user,
1491 sizeof(request.data.chauthtok.user) - 1);
1493 if (oldpass != NULL) {
1494 strncpy(request.data.chauthtok.oldpass, oldpass,
1495 sizeof(request.data.chauthtok.oldpass) - 1);
1496 } else {
1497 request.data.chauthtok.oldpass[0] = '\0';
1500 if (newpass != NULL) {
1501 strncpy(request.data.chauthtok.newpass, newpass,
1502 sizeof(request.data.chauthtok.newpass) - 1);
1503 } else {
1504 request.data.chauthtok.newpass[0] = '\0';
1507 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1508 request.flags = WBFLAG_PAM_KRB5 |
1509 WBFLAG_PAM_CONTACT_TRUSTDOM;
1512 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1513 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1516 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_CHAUTHTOK,
1517 &request, &response, user);
1519 if (ret == PAM_SUCCESS) {
1520 return ret;
1523 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1524 "NT_STATUS_BACKUP_CONTROLLER");
1525 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1526 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1527 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1528 "NT_STATUS_NO_LOGON_SERVERS");
1529 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1530 "NT_STATUS_ACCESS_DENIED");
1532 /* TODO: tell the min pwd length ? */
1533 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1534 "NT_STATUS_PWD_TOO_SHORT");
1536 /* TODO: tell the minage ? */
1537 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1538 "NT_STATUS_PWD_TOO_RECENT");
1540 /* TODO: tell the history length ? */
1541 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1542 "NT_STATUS_PWD_HISTORY_CONFLICT");
1544 if (!strcasecmp(response.data.auth.nt_status_string,
1545 "NT_STATUS_PASSWORD_RESTRICTION")) {
1547 char *pwd_restriction_string = NULL;
1548 SMB_TIME_T min_pwd_age;
1549 uint32_t reject_reason = response.data.auth.reject_reason;
1550 min_pwd_age = response.data.auth.policy.min_passwordage;
1552 /* FIXME: avoid to send multiple PAM messages after another */
1553 switch (reject_reason) {
1554 case -1:
1555 break;
1556 case SAMR_REJECT_OTHER:
1557 if ((min_pwd_age > 0) &&
1558 (pwd_last_set + min_pwd_age > time(NULL))) {
1559 PAM_WB_REMARK_DIRECT(ctx,
1560 "NT_STATUS_PWD_TOO_RECENT");
1562 break;
1563 case SAMR_REJECT_TOO_SHORT:
1564 PAM_WB_REMARK_DIRECT(ctx,
1565 "NT_STATUS_PWD_TOO_SHORT");
1566 break;
1567 case SAMR_REJECT_IN_HISTORY:
1568 PAM_WB_REMARK_DIRECT(ctx,
1569 "NT_STATUS_PWD_HISTORY_CONFLICT");
1570 break;
1571 case SAMR_REJECT_COMPLEXITY:
1572 _make_remark(ctx, PAM_ERROR_MSG,
1573 "Password does not meet "
1574 "complexity requirements");
1575 break;
1576 default:
1577 _pam_log_debug(ctx, LOG_DEBUG,
1578 "unknown password change "
1579 "reject reason: %d",
1580 reject_reason);
1581 break;
1584 pwd_restriction_string =
1585 _pam_compose_pwd_restriction_string(&response);
1586 if (pwd_restriction_string) {
1587 _make_remark(ctx, PAM_ERROR_MSG,
1588 pwd_restriction_string);
1589 SAFE_FREE(pwd_restriction_string);
1593 return ret;
1597 * Checks if a user has an account
1599 * return values:
1600 * 1 = User not found
1601 * 0 = OK
1602 * -1 = System error
1604 static int valid_user(struct pwb_context *ctx,
1605 const char *user)
1607 /* check not only if the user is available over NSS calls, also make
1608 * sure it's really a winbind user, this is important when stacking PAM
1609 * modules in the 'account' or 'password' facility. */
1611 struct passwd *pwd = NULL;
1612 struct winbindd_request request;
1613 struct winbindd_response response;
1614 int ret;
1616 ZERO_STRUCT(request);
1617 ZERO_STRUCT(response);
1619 pwd = getpwnam(user);
1620 if (pwd == NULL) {
1621 return 1;
1624 strncpy(request.data.username, user,
1625 sizeof(request.data.username) - 1);
1627 ret = pam_winbind_request_log(ctx, WINBINDD_GETPWNAM,
1628 &request, &response, user);
1630 switch (ret) {
1631 case PAM_USER_UNKNOWN:
1632 return 1;
1633 case PAM_SUCCESS:
1634 return 0;
1635 default:
1636 break;
1638 return -1;
1641 static char *_pam_delete(register char *xx)
1643 _pam_overwrite(xx);
1644 _pam_drop(xx);
1645 return NULL;
1649 * obtain a password from the user
1652 static int _winbind_read_password(struct pwb_context *ctx,
1653 unsigned int ctrl,
1654 const char *comment,
1655 const char *prompt1,
1656 const char *prompt2,
1657 const char **pass)
1659 int authtok_flag;
1660 int retval;
1661 const char *item;
1662 char *token;
1664 _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1667 * make sure nothing inappropriate gets returned
1670 *pass = token = NULL;
1673 * which authentication token are we getting?
1676 if (on(WINBIND__OLD_PASSWORD, ctrl)) {
1677 authtok_flag = PAM_OLDAUTHTOK;
1678 } else {
1679 authtok_flag = PAM_AUTHTOK;
1683 * should we obtain the password from a PAM item ?
1686 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) ||
1687 on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1688 retval = _pam_get_item(ctx->pamh, authtok_flag, &item);
1689 if (retval != PAM_SUCCESS) {
1690 /* very strange. */
1691 _pam_log(ctx, LOG_ALERT,
1692 "pam_get_item returned error "
1693 "to unix-read-password");
1694 return retval;
1695 } else if (item != NULL) { /* we have a password! */
1696 *pass = item;
1697 item = NULL;
1698 _pam_log(ctx, LOG_DEBUG,
1699 "pam_get_item returned a password");
1700 return PAM_SUCCESS;
1701 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1702 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
1703 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1704 && off(WINBIND__OLD_PASSWORD, ctrl)) {
1705 return PAM_AUTHTOK_RECOVER_ERR;
1709 * getting here implies we will have to get the password from the
1710 * user directly.
1714 struct pam_message msg[3], *pmsg[3];
1715 struct pam_response *resp;
1716 int i, replies;
1718 /* prepare to converse */
1720 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1721 pmsg[0] = &msg[0];
1722 msg[0].msg_style = PAM_TEXT_INFO;
1723 msg[0].msg = discard_const_p(char, comment);
1724 i = 1;
1725 } else {
1726 i = 0;
1729 pmsg[i] = &msg[i];
1730 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1731 msg[i++].msg = discard_const_p(char, prompt1);
1732 replies = 1;
1734 if (prompt2 != NULL) {
1735 pmsg[i] = &msg[i];
1736 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1737 msg[i++].msg = discard_const_p(char, prompt2);
1738 ++replies;
1740 /* so call the conversation expecting i responses */
1741 resp = NULL;
1742 retval = converse(ctx->pamh, i, pmsg, &resp);
1743 if (resp == NULL) {
1744 if (retval == PAM_SUCCESS) {
1745 retval = PAM_AUTHTOK_RECOVER_ERR;
1747 goto done;
1749 if (retval != PAM_SUCCESS) {
1750 _pam_drop_reply(resp, i);
1751 goto done;
1754 /* interpret the response */
1756 token = x_strdup(resp[i - replies].resp);
1757 if (!token) {
1758 _pam_log(ctx, LOG_NOTICE,
1759 "could not recover "
1760 "authentication token");
1761 retval = PAM_AUTHTOK_RECOVER_ERR;
1762 goto done;
1765 if (replies == 2) {
1766 /* verify that password entered correctly */
1767 if (!resp[i - 1].resp ||
1768 strcmp(token, resp[i - 1].resp)) {
1769 _pam_delete(token); /* mistyped */
1770 retval = PAM_AUTHTOK_RECOVER_ERR;
1771 _make_remark(ctx, PAM_ERROR_MSG,
1772 MISTYPED_PASS);
1777 * tidy up the conversation (resp_retcode) is ignored
1778 * -- what is it for anyway? AGM
1780 _pam_drop_reply(resp, i);
1783 done:
1784 if (retval != PAM_SUCCESS) {
1785 _pam_log_debug(ctx, LOG_DEBUG,
1786 "unable to obtain a password");
1787 return retval;
1789 /* 'token' is the entered password */
1791 /* we store this password as an item */
1793 retval = pam_set_item(ctx->pamh, authtok_flag, token);
1794 _pam_delete(token); /* clean it up */
1795 if (retval != PAM_SUCCESS ||
1796 (retval = _pam_get_item(ctx->pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1798 _pam_log(ctx, LOG_CRIT, "error manipulating password");
1799 return retval;
1803 *pass = item;
1804 item = NULL; /* break link to password */
1806 return PAM_SUCCESS;
1809 static const char *get_conf_item_string(struct pwb_context *ctx,
1810 const char *item,
1811 int config_flag)
1813 int i = 0;
1814 const char *parm_opt = NULL;
1816 if (!(ctx->ctrl & config_flag)) {
1817 goto out;
1820 /* let the pam opt take precedence over the pam_winbind.conf option */
1821 for (i=0; i<ctx->argc; i++) {
1823 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1824 char *p;
1826 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1827 _pam_log(ctx, LOG_INFO,
1828 "no \"=\" delimiter for \"%s\" found\n",
1829 item);
1830 goto out;
1832 _pam_log_debug(ctx, LOG_INFO,
1833 "PAM config: %s '%s'\n", item, p+1);
1834 return p + 1;
1838 if (ctx->dict) {
1839 char *key = NULL;
1841 if (!asprintf(&key, "global:%s", item)) {
1842 goto out;
1845 parm_opt = iniparser_getstr(ctx->dict, key);
1846 SAFE_FREE(key);
1848 _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n",
1849 item, parm_opt);
1851 out:
1852 return parm_opt;
1855 static int get_config_item_int(struct pwb_context *ctx,
1856 const char *item,
1857 int config_flag)
1859 int i, parm_opt = -1;
1861 if (!(ctx->ctrl & config_flag)) {
1862 goto out;
1865 /* let the pam opt take precedence over the pam_winbind.conf option */
1866 for (i = 0; i < ctx->argc; i++) {
1868 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1869 char *p;
1871 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1872 _pam_log(ctx, LOG_INFO,
1873 "no \"=\" delimiter for \"%s\" found\n",
1874 item);
1875 goto out;
1877 parm_opt = atoi(p + 1);
1878 _pam_log_debug(ctx, LOG_INFO,
1879 "PAM config: %s '%d'\n",
1880 item, parm_opt);
1881 return parm_opt;
1885 if (ctx->dict) {
1886 char *key = NULL;
1888 if (!asprintf(&key, "global:%s", item)) {
1889 goto out;
1892 parm_opt = iniparser_getint(ctx->dict, key, -1);
1893 SAFE_FREE(key);
1895 _pam_log_debug(ctx, LOG_INFO,
1896 "CONFIG file: %s '%d'\n",
1897 item, parm_opt);
1899 out:
1900 return parm_opt;
1903 static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx)
1905 return get_conf_item_string(ctx, "krb5_ccache_type",
1906 WINBIND_KRB5_CCACHE_TYPE);
1909 static const char *get_member_from_config(struct pwb_context *ctx)
1911 const char *ret = NULL;
1912 ret = get_conf_item_string(ctx, "require_membership_of",
1913 WINBIND_REQUIRED_MEMBERSHIP);
1914 if (ret) {
1915 return ret;
1917 return get_conf_item_string(ctx, "require-membership-of",
1918 WINBIND_REQUIRED_MEMBERSHIP);
1921 static int get_warn_pwd_expire_from_config(struct pwb_context *ctx)
1923 int ret;
1924 ret = get_config_item_int(ctx, "warn_pwd_expire",
1925 WINBIND_WARN_PWD_EXPIRE);
1926 /* no or broken setting */
1927 if (ret <= 0) {
1928 return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES;
1930 return ret;
1934 * Retrieve the winbind separator.
1936 * @param ctx PAM winbind context.
1938 * @return string separator character. NULL on failure.
1941 static char winbind_get_separator(struct pwb_context *ctx)
1943 struct winbindd_request request;
1944 struct winbindd_response response;
1946 ZERO_STRUCT(request);
1947 ZERO_STRUCT(response);
1949 if (pam_winbind_request_log(ctx, WINBINDD_INFO,
1950 &request, &response, NULL)) {
1951 return '\0';
1954 return response.data.info.winbind_separator;
1958 * Convert a upn to a name.
1960 * @param ctx PAM winbind context.
1961 * @param upn USer UPN to be trabslated.
1963 * @return converted name. NULL pointer on failure. Caller needs to free.
1966 static char* winbind_upn_to_username(struct pwb_context *ctx,
1967 const char *upn)
1969 struct winbindd_request req;
1970 struct winbindd_response resp;
1971 int retval;
1972 char *account_name;
1973 int account_name_len;
1974 char sep;
1976 /* This cannot work when the winbind separator = @ */
1978 sep = winbind_get_separator(ctx);
1979 if (!sep || sep == '@') {
1980 return NULL;
1983 /* Convert the UPN to a SID */
1985 ZERO_STRUCT(req);
1986 ZERO_STRUCT(resp);
1988 strncpy(req.data.name.dom_name, "",
1989 sizeof(req.data.name.dom_name) - 1);
1990 strncpy(req.data.name.name, upn,
1991 sizeof(req.data.name.name) - 1);
1992 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
1993 &req, &resp, upn);
1994 if (retval != PAM_SUCCESS) {
1995 return NULL;
1998 /* Convert the the SID back to the sAMAccountName */
2000 ZERO_STRUCT(req);
2001 strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
2002 ZERO_STRUCT(resp);
2003 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPSID,
2004 &req, &resp, upn);
2005 if (retval != PAM_SUCCESS) {
2006 return NULL;
2009 account_name_len = asprintf(&account_name, "%s\\%s",
2010 resp.data.name.dom_name,
2011 resp.data.name.name);
2013 return account_name;
2016 PAM_EXTERN
2017 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
2018 int argc, const char **argv)
2020 const char *username;
2021 const char *password;
2022 const char *member = NULL;
2023 const char *cctype = NULL;
2024 int warn_pwd_expire;
2025 int retval = PAM_AUTH_ERR;
2026 char *username_ret = NULL;
2027 char *new_authtok_required = NULL;
2028 char *real_username = NULL;
2029 struct pwb_context *ctx = NULL;
2031 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2032 if (retval) {
2033 goto out;
2036 _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx);
2038 /* Get the username */
2039 retval = pam_get_user(pamh, &username, NULL);
2040 if ((retval != PAM_SUCCESS) || (!username)) {
2041 _pam_log_debug(ctx, LOG_DEBUG,
2042 "can not get the username");
2043 retval = PAM_SERVICE_ERR;
2044 goto out;
2048 #if defined(AIX)
2049 /* Decode the user name since AIX does not support logn user
2050 names by default. The name is encoded as _#uid. */
2052 if (username[0] == '_') {
2053 uid_t id = atoi(&username[1]);
2054 struct passwd *pw = NULL;
2056 if ((id!=0) && ((pw = getpwuid(id)) != NULL)) {
2057 real_username = strdup(pw->pw_name);
2060 #endif
2062 if (!real_username) {
2063 /* Just making a copy of the username we got from PAM */
2064 if ((real_username = strdup(username)) == NULL) {
2065 _pam_log_debug(ctx, LOG_DEBUG,
2066 "memory allocation failure when copying "
2067 "username");
2068 retval = PAM_SERVICE_ERR;
2069 goto out;
2073 /* Maybe this was a UPN */
2075 if (strchr(real_username, '@') != NULL) {
2076 char *samaccountname = NULL;
2078 samaccountname = winbind_upn_to_username(ctx,
2079 real_username);
2080 if (samaccountname) {
2081 free(real_username);
2082 real_username = samaccountname;
2086 retval = _winbind_read_password(ctx, ctx->ctrl, NULL,
2087 "Password: ", NULL,
2088 &password);
2090 if (retval != PAM_SUCCESS) {
2091 _pam_log(ctx, LOG_ERR,
2092 "Could not retrieve user's password");
2093 retval = PAM_AUTHTOK_ERR;
2094 goto out;
2097 /* Let's not give too much away in the log file */
2099 #ifdef DEBUG_PASSWORD
2100 _pam_log_debug(ctx, LOG_INFO,
2101 "Verify user '%s' with password '%s'",
2102 real_username, password);
2103 #else
2104 _pam_log_debug(ctx, LOG_INFO,
2105 "Verify user '%s'", real_username);
2106 #endif
2108 member = get_member_from_config(ctx);
2109 cctype = get_krb5_cc_type_from_config(ctx);
2110 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2112 /* Now use the username to look up password */
2113 retval = winbind_auth_request(ctx, real_username, password,
2114 member, cctype, warn_pwd_expire, NULL,
2115 NULL, &username_ret);
2117 if (retval == PAM_NEW_AUTHTOK_REQD ||
2118 retval == PAM_AUTHTOK_EXPIRED) {
2120 char *new_authtok_required_during_auth = NULL;
2122 if (!asprintf(&new_authtok_required, "%d", retval)) {
2123 retval = PAM_BUF_ERR;
2124 goto out;
2127 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2128 new_authtok_required,
2129 _pam_winbind_cleanup_func);
2131 retval = PAM_SUCCESS;
2133 if (!asprintf(&new_authtok_required_during_auth, "%d", true)) {
2134 retval = PAM_BUF_ERR;
2135 goto out;
2138 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2139 new_authtok_required_during_auth,
2140 _pam_winbind_cleanup_func);
2142 goto out;
2145 out:
2146 if (username_ret) {
2147 pam_set_item (pamh, PAM_USER, username_ret);
2148 _pam_log_debug(ctx, LOG_INFO,
2149 "Returned user was '%s'", username_ret);
2150 free(username_ret);
2153 if (real_username) {
2154 free(real_username);
2157 if (!new_authtok_required) {
2158 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
2161 if (retval != PAM_SUCCESS) {
2162 _pam_free_data_info3(pamh);
2165 _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
2167 _pam_winbind_free_context(ctx);
2169 return retval;
2172 PAM_EXTERN
2173 int pam_sm_setcred(pam_handle_t *pamh, int flags,
2174 int argc, const char **argv)
2176 int ret = PAM_SYSTEM_ERR;
2177 struct pwb_context *ctx = NULL;
2179 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2180 if (ret) {
2181 goto out;
2184 _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx);
2186 switch (flags & ~PAM_SILENT) {
2188 case PAM_DELETE_CRED:
2189 ret = pam_sm_close_session(pamh, flags, argc, argv);
2190 break;
2191 case PAM_REFRESH_CRED:
2192 _pam_log_debug(ctx, LOG_WARNING,
2193 "PAM_REFRESH_CRED not implemented");
2194 ret = PAM_SUCCESS;
2195 break;
2196 case PAM_REINITIALIZE_CRED:
2197 _pam_log_debug(ctx, LOG_WARNING,
2198 "PAM_REINITIALIZE_CRED not implemented");
2199 ret = PAM_SUCCESS;
2200 break;
2201 case PAM_ESTABLISH_CRED:
2202 _pam_log_debug(ctx, LOG_WARNING,
2203 "PAM_ESTABLISH_CRED not implemented");
2204 ret = PAM_SUCCESS;
2205 break;
2206 default:
2207 ret = PAM_SYSTEM_ERR;
2208 break;
2211 out:
2213 _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret);
2215 _pam_winbind_free_context(ctx);
2217 return ret;
2221 * Account management. We want to verify that the account exists
2222 * before returning PAM_SUCCESS
2224 PAM_EXTERN
2225 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
2226 int argc, const char **argv)
2228 const char *username;
2229 int ret = PAM_USER_UNKNOWN;
2230 void *tmp = NULL;
2231 struct pwb_context *ctx = NULL;
2233 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2234 if (ret) {
2235 goto out;
2238 _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx);
2241 /* Get the username */
2242 ret = pam_get_user(pamh, &username, NULL);
2243 if ((ret != PAM_SUCCESS) || (!username)) {
2244 _pam_log_debug(ctx, LOG_DEBUG,
2245 "can not get the username");
2246 ret = PAM_SERVICE_ERR;
2247 goto out;
2250 /* Verify the username */
2251 ret = valid_user(ctx, username);
2252 switch (ret) {
2253 case -1:
2254 /* some sort of system error. The log was already printed */
2255 ret = PAM_SERVICE_ERR;
2256 goto out;
2257 case 1:
2258 /* the user does not exist */
2259 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
2260 username);
2261 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
2262 ret = PAM_IGNORE;
2263 goto out;
2265 ret = PAM_USER_UNKNOWN;
2266 goto out;
2267 case 0:
2268 pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2269 (const void **)&tmp);
2270 if (tmp != NULL) {
2271 ret = atoi((const char *)tmp);
2272 switch (ret) {
2273 case PAM_AUTHTOK_EXPIRED:
2274 /* fall through, since new token is required in this case */
2275 case PAM_NEW_AUTHTOK_REQD:
2276 _pam_log(ctx, LOG_WARNING,
2277 "pam_sm_acct_mgmt success but %s is set",
2278 PAM_WINBIND_NEW_AUTHTOK_REQD);
2279 _pam_log(ctx, LOG_NOTICE,
2280 "user '%s' needs new password",
2281 username);
2282 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
2283 ret = PAM_NEW_AUTHTOK_REQD;
2284 goto out;
2285 default:
2286 _pam_log(ctx, LOG_WARNING,
2287 "pam_sm_acct_mgmt success");
2288 _pam_log(ctx, LOG_NOTICE,
2289 "user '%s' granted access", username);
2290 ret = PAM_SUCCESS;
2291 goto out;
2295 /* Otherwise, the authentication looked good */
2296 _pam_log(ctx, LOG_NOTICE,
2297 "user '%s' granted access", username);
2298 ret = PAM_SUCCESS;
2299 goto out;
2300 default:
2301 /* we don't know anything about this return value */
2302 _pam_log(ctx, LOG_ERR,
2303 "internal module error (ret = %d, user = '%s')",
2304 ret, username);
2305 ret = PAM_SERVICE_ERR;
2306 goto out;
2309 /* should not be reached */
2310 ret = PAM_IGNORE;
2312 out:
2314 _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret);
2316 _pam_winbind_free_context(ctx);
2318 return ret;
2321 PAM_EXTERN
2322 int pam_sm_open_session(pam_handle_t *pamh, int flags,
2323 int argc, const char **argv)
2325 int ret = PAM_SYSTEM_ERR;
2326 struct pwb_context *ctx = NULL;
2328 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2329 if (ret) {
2330 goto out;
2333 _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx);
2335 ret = PAM_SUCCESS;
2337 out:
2338 _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret);
2340 _pam_winbind_free_context(ctx);
2342 return ret;
2345 PAM_EXTERN
2346 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2347 int argc, const char **argv)
2349 int retval = PAM_SUCCESS;
2350 struct pwb_context *ctx = NULL;
2352 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2353 if (retval) {
2354 goto out;
2357 _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx);
2359 if (!(flags & PAM_DELETE_CRED)) {
2360 retval = PAM_SUCCESS;
2361 goto out;
2364 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2366 /* destroy the ccache here */
2367 struct winbindd_request request;
2368 struct winbindd_response response;
2369 const char *user;
2370 const char *ccname = NULL;
2371 struct passwd *pwd = NULL;
2373 ZERO_STRUCT(request);
2374 ZERO_STRUCT(response);
2376 retval = pam_get_user(pamh, &user, "Username: ");
2377 if (retval) {
2378 _pam_log(ctx, LOG_ERR,
2379 "could not identify user");
2380 goto out;
2383 if (user == NULL) {
2384 _pam_log(ctx, LOG_ERR,
2385 "username was NULL!");
2386 retval = PAM_USER_UNKNOWN;
2387 goto out;
2390 _pam_log_debug(ctx, LOG_DEBUG,
2391 "username [%s] obtained", user);
2393 ccname = pam_getenv(pamh, "KRB5CCNAME");
2394 if (ccname == NULL) {
2395 _pam_log_debug(ctx, LOG_DEBUG,
2396 "user has no KRB5CCNAME environment");
2399 strncpy(request.data.logoff.user, user,
2400 sizeof(request.data.logoff.user) - 1);
2402 if (ccname) {
2403 strncpy(request.data.logoff.krb5ccname, ccname,
2404 sizeof(request.data.logoff.krb5ccname) - 1);
2407 pwd = getpwnam(user);
2408 if (pwd == NULL) {
2409 retval = PAM_USER_UNKNOWN;
2410 goto out;
2412 request.data.logoff.uid = pwd->pw_uid;
2414 request.flags = WBFLAG_PAM_KRB5 |
2415 WBFLAG_PAM_CONTACT_TRUSTDOM;
2417 retval = pam_winbind_request_log(ctx,
2418 WINBINDD_PAM_LOGOFF,
2419 &request, &response, user);
2422 out:
2424 _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, retval);
2426 _pam_winbind_free_context(ctx);
2428 return retval;
2432 * evaluate whether we need to re-authenticate with kerberos after a
2433 * password change
2435 * @param ctx PAM winbind context.
2436 * @param user The username
2438 * @return boolean Returns true if required, false if not.
2441 static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx,
2442 const char *user)
2445 /* Make sure that we only do this if a) the chauthtok got initiated
2446 * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any
2447 * later password change via the "passwd" command if done by the user
2448 * itself
2449 * NB. If we login from gdm or xdm and the password expires,
2450 * we change the password, but there is no memory cache.
2451 * Thus, even for passthrough login, we should do the
2452 * authentication again to update memory cache.
2453 * --- BoYang
2454 * */
2456 char *new_authtok_reqd_during_auth = NULL;
2457 struct passwd *pwd = NULL;
2459 _pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2460 &new_authtok_reqd_during_auth);
2461 pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2462 NULL, NULL);
2464 if (new_authtok_reqd_during_auth) {
2465 return true;
2468 pwd = getpwnam(user);
2469 if (!pwd) {
2470 return false;
2473 if (getuid() == pwd->pw_uid) {
2474 return true;
2477 return false;
2481 PAM_EXTERN
2482 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2483 int argc, const char **argv)
2485 unsigned int lctrl;
2486 int ret;
2487 bool cached_login = false;
2489 /* <DO NOT free() THESE> */
2490 const char *user;
2491 char *pass_old, *pass_new;
2492 /* </DO NOT free() THESE> */
2494 char *Announce;
2496 int retry = 0;
2497 char *username_ret = NULL;
2498 struct winbindd_response response;
2499 struct pwb_context *ctx = NULL;
2501 ZERO_STRUCT(response);
2503 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2504 if (ret) {
2505 goto out;
2508 _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx);
2510 cached_login = (ctx->ctrl & WINBIND_CACHED_LOGIN);
2512 /* clearing offline bit for auth */
2513 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2516 * First get the name of a user
2518 ret = pam_get_user(pamh, &user, "Username: ");
2519 if (ret) {
2520 _pam_log(ctx, LOG_ERR,
2521 "password - could not identify user");
2522 goto out;
2525 if (user == NULL) {
2526 _pam_log(ctx, LOG_ERR, "username was NULL!");
2527 ret = PAM_USER_UNKNOWN;
2528 goto out;
2531 _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user);
2533 /* check if this is really a user in winbindd, not only in NSS */
2534 ret = valid_user(ctx, user);
2535 switch (ret) {
2536 case 1:
2537 ret = PAM_USER_UNKNOWN;
2538 goto out;
2539 case -1:
2540 ret = PAM_SYSTEM_ERR;
2541 goto out;
2542 default:
2543 break;
2547 * obtain and verify the current password (OLDAUTHTOK) for
2548 * the user.
2551 if (flags & PAM_PRELIM_CHECK) {
2552 time_t pwdlastset_prelim = 0;
2554 /* instruct user what is happening */
2555 #define greeting "Changing password for "
2556 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2557 if (Announce == NULL) {
2558 _pam_log(ctx, LOG_CRIT,
2559 "password - out of memory");
2560 ret = PAM_BUF_ERR;
2561 goto out;
2563 (void) strcpy(Announce, greeting);
2564 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2565 #undef greeting
2567 lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD;
2568 ret = _winbind_read_password(ctx, lctrl,
2569 Announce,
2570 "(current) NT password: ",
2571 NULL,
2572 (const char **) &pass_old);
2573 if (ret != PAM_SUCCESS) {
2574 _pam_log(ctx, LOG_NOTICE,
2575 "password - (old) token not obtained");
2576 goto out;
2579 /* verify that this is the password for this user */
2581 ret = winbind_auth_request(ctx, user, pass_old,
2582 NULL, NULL, 0, &response,
2583 &pwdlastset_prelim, NULL);
2585 if (ret != PAM_ACCT_EXPIRED &&
2586 ret != PAM_AUTHTOK_EXPIRED &&
2587 ret != PAM_NEW_AUTHTOK_REQD &&
2588 ret != PAM_SUCCESS) {
2589 pass_old = NULL;
2590 goto out;
2593 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2594 (void *)pwdlastset_prelim, NULL);
2596 ret = pam_set_item(pamh, PAM_OLDAUTHTOK,
2597 (const void *) pass_old);
2598 pass_old = NULL;
2599 if (ret != PAM_SUCCESS) {
2600 _pam_log(ctx, LOG_CRIT,
2601 "failed to set PAM_OLDAUTHTOK");
2603 } else if (flags & PAM_UPDATE_AUTHTOK) {
2605 time_t pwdlastset_update = 0;
2608 * obtain the proposed password
2612 * get the old token back.
2615 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2617 if (ret != PAM_SUCCESS) {
2618 _pam_log(ctx, LOG_NOTICE,
2619 "user not authenticated");
2620 goto out;
2623 lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2625 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2626 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2628 retry = 0;
2629 ret = PAM_AUTHTOK_ERR;
2630 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2632 * use_authtok is to force the use of a previously entered
2633 * password -- needed for pluggable password strength checking
2636 ret = _winbind_read_password(ctx, lctrl,
2637 NULL,
2638 "Enter new NT password: ",
2639 "Retype new NT password: ",
2640 (const char **)&pass_new);
2642 if (ret != PAM_SUCCESS) {
2643 _pam_log_debug(ctx, LOG_ALERT,
2644 "password - "
2645 "new password not obtained");
2646 pass_old = NULL;/* tidy up */
2647 goto out;
2651 * At this point we know who the user is and what they
2652 * propose as their new password. Verify that the new
2653 * password is acceptable.
2656 if (pass_new[0] == '\0') {/* "\0" password = NULL */
2657 pass_new = NULL;
2662 * By reaching here we have approved the passwords and must now
2663 * rebuild the password database file.
2665 _pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2666 &pwdlastset_update);
2669 * if cached creds were enabled, make sure to set the
2670 * WINBIND_CACHED_LOGIN bit here in order to have winbindd
2671 * update the cached creds storage - gd
2673 if (cached_login) {
2674 ctx->ctrl |= WINBIND_CACHED_LOGIN;
2677 ret = winbind_chauthtok_request(ctx, user, pass_old,
2678 pass_new, pwdlastset_update);
2679 if (ret) {
2680 _pam_overwrite(pass_new);
2681 _pam_overwrite(pass_old);
2682 pass_old = pass_new = NULL;
2683 goto out;
2686 if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) {
2688 const char *member = NULL;
2689 const char *cctype = NULL;
2690 int warn_pwd_expire;
2692 member = get_member_from_config(ctx);
2693 cctype = get_krb5_cc_type_from_config(ctx);
2694 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2696 /* Keep WINBIND_CACHED_LOGIN bit for
2697 * authentication after changing the password.
2698 * This will update the cached credentials in case
2699 * that winbindd_dual_pam_chauthtok() fails
2700 * to update them.
2701 * --- BoYang
2702 * */
2704 ret = winbind_auth_request(ctx, user, pass_new,
2705 member, cctype, 0, &response,
2706 NULL, &username_ret);
2707 _pam_overwrite(pass_new);
2708 _pam_overwrite(pass_old);
2709 pass_old = pass_new = NULL;
2711 if (ret == PAM_SUCCESS) {
2713 /* warn a user if the password is about to
2714 * expire soon */
2715 _pam_warn_password_expiry(ctx, &response,
2716 warn_pwd_expire,
2717 NULL);
2719 /* set some info3 info for other modules in the
2720 * stack */
2721 _pam_set_data_info3(ctx, &response);
2723 /* put krb5ccname into env */
2724 _pam_setup_krb5_env(ctx,
2725 response.data.auth.krb5ccname);
2727 if (username_ret) {
2728 pam_set_item(pamh, PAM_USER,
2729 username_ret);
2730 _pam_log_debug(ctx, LOG_INFO,
2731 "Returned user was '%s'",
2732 username_ret);
2733 free(username_ret);
2737 goto out;
2739 } else {
2740 ret = PAM_SERVICE_ERR;
2743 out:
2745 /* Deal with offline errors. */
2746 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2747 "NT_STATUS_NO_LOGON_SERVERS");
2748 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2749 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2750 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2751 "NT_STATUS_ACCESS_DENIED");
2753 _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);
2755 _pam_winbind_free_context(ctx);
2757 return ret;
2760 #ifdef PAM_STATIC
2762 /* static module data */
2764 struct pam_module _pam_winbind_modstruct = {
2765 MODULE_NAME,
2766 pam_sm_authenticate,
2767 pam_sm_setcred,
2768 pam_sm_acct_mgmt,
2769 pam_sm_open_session,
2770 pam_sm_close_session,
2771 pam_sm_chauthtok
2774 #endif
2777 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
2778 * Copyright (c) Tim Potter <tpot@samba.org> 2000
2779 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2780 * Copyright (c) Guenther Deschner <gd@samba.org> 2005-2008
2781 * Copyright (c) Jan Rêkorajski 1999.
2782 * Copyright (c) Andrew G. Morgan 1996-8.
2783 * Copyright (c) Alex O. Yuriev, 1996.
2784 * Copyright (c) Cristian Gafton 1996.
2785 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
2787 * Redistribution and use in source and binary forms, with or without
2788 * modification, are permitted provided that the following conditions
2789 * are met:
2790 * 1. Redistributions of source code must retain the above copyright
2791 * notice, and the entire permission notice in its entirety,
2792 * including the disclaimer of warranties.
2793 * 2. Redistributions in binary form must reproduce the above copyright
2794 * notice, this list of conditions and the following disclaimer in the
2795 * documentation and/or other materials provided with the distribution.
2796 * 3. The name of the author may not be used to endorse or promote
2797 * products derived from this software without specific prior
2798 * written permission.
2800 * ALTERNATIVELY, this product may be distributed under the terms of
2801 * the GNU Public License, in which case the provisions of the GPL are
2802 * required INSTEAD OF the above restrictions. (This clause is
2803 * necessary due to a potential bad interaction between the GPL and
2804 * the restrictions contained in a BSD-style copyright.)
2806 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2807 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2808 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2809 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2810 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2811 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2812 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2813 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2814 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2815 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2816 * OF THE POSSIBILITY OF SUCH DAMAGE.