pam_winbind: add _pam_error_code_str().
[Samba.git] / source3 / nsswitch / pam_winbind.c
blob872a1b72ea41ef71c775e61a27cdcbb756480e40
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", ctx->pamh, retval); \
98 _pam_log_state(ctx); \
99 } while (0)
101 /* data tokens */
103 #define MAX_PASSWD_TRIES 3
106 * Work around the pam API that has functions with void ** as parameters
107 * These lead to strict aliasing warnings with gcc.
109 static int _pam_get_item(const pam_handle_t *pamh,
110 int item_type,
111 const void *_item)
113 const void **item = (const void **)_item;
114 return pam_get_item(pamh, item_type, item);
116 static int _pam_get_data(const pam_handle_t *pamh,
117 const char *module_data_name,
118 const void *_data)
120 const void **data = (const void **)_data;
121 return pam_get_data(pamh, module_data_name, data);
124 /* some syslogging */
126 #ifdef HAVE_PAM_VSYSLOG
127 static void _pam_log_int(const pam_handle_t *pamh,
128 int err,
129 const char *format,
130 va_list args)
132 pam_vsyslog(pamh, err, format, args);
134 #else
135 static void _pam_log_int(const pam_handle_t *pamh,
136 int err,
137 const char *format,
138 va_list args)
140 char *format2 = NULL;
141 const char *service;
143 _pam_get_item(pamh, PAM_SERVICE, &service);
145 format2 = (char *)malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
146 if (format2 == NULL) {
147 /* what else todo ? */
148 vsyslog(err, format, args);
149 return;
152 sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
153 vsyslog(err, format2, args);
154 SAFE_FREE(format2);
156 #endif /* HAVE_PAM_VSYSLOG */
158 static bool _pam_log_is_silent(int ctrl)
160 return on(ctrl, WINBIND_SILENT);
163 static void _pam_log(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
164 static void _pam_log(struct pwb_context *r, int err, const char *format, ...)
166 va_list args;
168 if (_pam_log_is_silent(r->ctrl)) {
169 return;
172 va_start(args, format);
173 _pam_log_int(r->pamh, err, format, args);
174 va_end(args);
176 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
177 static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
179 va_list args;
181 if (_pam_log_is_silent(ctrl)) {
182 return;
185 va_start(args, format);
186 _pam_log_int(pamh, err, format, args);
187 va_end(args);
190 static bool _pam_log_is_debug_enabled(int ctrl)
192 if (ctrl == -1) {
193 return false;
196 if (_pam_log_is_silent(ctrl)) {
197 return false;
200 if (!(ctrl & WINBIND_DEBUG_ARG)) {
201 return false;
204 return true;
207 static bool _pam_log_is_debug_state_enabled(int ctrl)
209 if (!(ctrl & WINBIND_DEBUG_STATE)) {
210 return false;
213 return _pam_log_is_debug_enabled(ctrl);
216 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
217 static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...)
219 va_list args;
221 if (!_pam_log_is_debug_enabled(r->ctrl)) {
222 return;
225 va_start(args, format);
226 _pam_log_int(r->pamh, err, format, args);
227 va_end(args);
229 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5);
230 static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...)
232 va_list args;
234 if (!_pam_log_is_debug_enabled(ctrl)) {
235 return;
238 va_start(args, format);
239 _pam_log_int(pamh, err, format, args);
240 va_end(args);
243 static void _pam_log_state_datum(struct pwb_context *ctx,
244 int item_type,
245 const char *key,
246 int is_string)
248 const void *data = NULL;
249 if (item_type != 0) {
250 pam_get_item(ctx->pamh, item_type, &data);
251 } else {
252 pam_get_data(ctx->pamh, key, &data);
254 if (data != NULL) {
255 const char *type = (item_type != 0) ? "ITEM" : "DATA";
256 if (is_string != 0) {
257 _pam_log_debug(ctx, LOG_DEBUG,
258 "[pamh: %p] STATE: %s(%s) = \"%s\" (%p)",
259 ctx->pamh, type, key, (const char *)data,
260 data);
261 } else {
262 _pam_log_debug(ctx, LOG_DEBUG,
263 "[pamh: %p] STATE: %s(%s) = %p",
264 ctx->pamh, type, key, data);
269 #define _PAM_LOG_STATE_DATA_POINTER(ctx, module_data_name) \
270 _pam_log_state_datum(ctx, 0, module_data_name, 0)
272 #define _PAM_LOG_STATE_DATA_STRING(ctx, module_data_name) \
273 _pam_log_state_datum(ctx, 0, module_data_name, 1)
275 #define _PAM_LOG_STATE_ITEM_POINTER(ctx, item_type) \
276 _pam_log_state_datum(ctx, item_type, #item_type, 0)
278 #define _PAM_LOG_STATE_ITEM_STRING(ctx, item_type) \
279 _pam_log_state_datum(ctx, item_type, #item_type, 1)
281 #ifdef DEBUG_PASSWORD
282 #define _LOG_PASSWORD_AS_STRING 1
283 #else
284 #define _LOG_PASSWORD_AS_STRING 0
285 #endif
287 #define _PAM_LOG_STATE_ITEM_PASSWORD(ctx, item_type) \
288 _pam_log_state_datum(ctx, item_type, #item_type, \
289 _LOG_PASSWORD_AS_STRING)
291 static void _pam_log_state(struct pwb_context *ctx)
293 if (!_pam_log_is_debug_state_enabled(ctx->ctrl)) {
294 return;
297 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_SERVICE);
298 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER);
299 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_TTY);
300 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RHOST);
301 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RUSER);
302 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_OLDAUTHTOK);
303 _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_AUTHTOK);
304 _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER_PROMPT);
305 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_CONV);
306 #ifdef PAM_FAIL_DELAY
307 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_FAIL_DELAY);
308 #endif
309 #ifdef PAM_REPOSITORY
310 _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_REPOSITORY);
311 #endif
313 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_HOMEDIR);
314 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSCRIPT);
315 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSERVER);
316 _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_PROFILEPATH);
317 _PAM_LOG_STATE_DATA_STRING(ctx,
318 PAM_WINBIND_NEW_AUTHTOK_REQD);
319 /* Use atoi to get PAM result code */
320 _PAM_LOG_STATE_DATA_STRING(ctx,
321 PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH);
322 _PAM_LOG_STATE_DATA_POINTER(ctx, PAM_WINBIND_PWD_LAST_SET);
325 static int _pam_parse(const pam_handle_t *pamh,
326 int flags,
327 int argc,
328 const char **argv,
329 dictionary **result_d)
331 int ctrl = 0;
332 const char *config_file = NULL;
333 int i;
334 const char **v;
335 dictionary *d = NULL;
337 if (flags & PAM_SILENT) {
338 ctrl |= WINBIND_SILENT;
341 for (i=argc,v=argv; i-- > 0; ++v) {
342 if (!strncasecmp(*v, "config", strlen("config"))) {
343 ctrl |= WINBIND_CONFIG_FILE;
344 config_file = v[i];
345 break;
349 if (config_file == NULL) {
350 config_file = PAM_WINBIND_CONFIG_FILE;
353 d = iniparser_load(config_file);
354 if (d == NULL) {
355 goto config_from_pam;
358 if (iniparser_getboolean(d, "global:debug", false)) {
359 ctrl |= WINBIND_DEBUG_ARG;
362 if (iniparser_getboolean(d, "global:debug_state", false)) {
363 ctrl |= WINBIND_DEBUG_STATE;
366 if (iniparser_getboolean(d, "global:cached_login", false)) {
367 ctrl |= WINBIND_CACHED_LOGIN;
370 if (iniparser_getboolean(d, "global:krb5_auth", false)) {
371 ctrl |= WINBIND_KRB5_AUTH;
374 if (iniparser_getboolean(d, "global:silent", false)) {
375 ctrl |= WINBIND_SILENT;
378 if (iniparser_getstr(d, "global:krb5_ccache_type") != NULL) {
379 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
382 if ((iniparser_getstr(d, "global:require-membership-of") != NULL) ||
383 (iniparser_getstr(d, "global:require_membership_of") != NULL)) {
384 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
387 if (iniparser_getboolean(d, "global:try_first_pass", false)) {
388 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
391 if (iniparser_getint(d, "global:warn_pwd_expire", 0)) {
392 ctrl |= WINBIND_WARN_PWD_EXPIRE;
395 config_from_pam:
396 /* step through arguments */
397 for (i=argc,v=argv; i-- > 0; ++v) {
399 /* generic options */
400 if (!strcmp(*v,"debug"))
401 ctrl |= WINBIND_DEBUG_ARG;
402 else if (!strcasecmp(*v, "debug_state"))
403 ctrl |= WINBIND_DEBUG_STATE;
404 else if (!strcasecmp(*v, "silent"))
405 ctrl |= WINBIND_SILENT;
406 else if (!strcasecmp(*v, "use_authtok"))
407 ctrl |= WINBIND_USE_AUTHTOK_ARG;
408 else if (!strcasecmp(*v, "use_first_pass"))
409 ctrl |= WINBIND_USE_FIRST_PASS_ARG;
410 else if (!strcasecmp(*v, "try_first_pass"))
411 ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
412 else if (!strcasecmp(*v, "unknown_ok"))
413 ctrl |= WINBIND_UNKNOWN_OK_ARG;
414 else if (!strncasecmp(*v, "require_membership_of",
415 strlen("require_membership_of")))
416 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
417 else if (!strncasecmp(*v, "require-membership-of",
418 strlen("require-membership-of")))
419 ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
420 else if (!strcasecmp(*v, "krb5_auth"))
421 ctrl |= WINBIND_KRB5_AUTH;
422 else if (!strncasecmp(*v, "krb5_ccache_type",
423 strlen("krb5_ccache_type")))
424 ctrl |= WINBIND_KRB5_CCACHE_TYPE;
425 else if (!strcasecmp(*v, "cached_login"))
426 ctrl |= WINBIND_CACHED_LOGIN;
427 else {
428 __pam_log(pamh, ctrl, LOG_ERR,
429 "pam_parse: unknown option: %s", *v);
430 return -1;
435 if (result_d) {
436 *result_d = d;
437 } else {
438 if (d) {
439 iniparser_freedict(d);
443 return ctrl;
446 static void _pam_winbind_free_context(struct pwb_context *ctx)
448 if (ctx->dict) {
449 iniparser_freedict(ctx->dict);
452 SAFE_FREE(ctx);
455 static int _pam_winbind_init_context(pam_handle_t *pamh,
456 int flags,
457 int argc,
458 const char **argv,
459 struct pwb_context **ctx_p)
461 struct pwb_context *r = NULL;
463 r = (struct pwb_context *)malloc(sizeof(struct pwb_context));
464 if (!r) {
465 return PAM_BUF_ERR;
468 ZERO_STRUCTP(r);
470 r->pamh = pamh;
471 r->flags = flags;
472 r->argc = argc;
473 r->argv = argv;
474 r->ctrl = _pam_parse(pamh, flags, argc, argv, &r->dict);
475 if (r->ctrl == -1) {
476 _pam_winbind_free_context(r);
477 return PAM_SYSTEM_ERR;
480 *ctx_p = r;
482 return PAM_SUCCESS;
485 static void _pam_winbind_cleanup_func(pam_handle_t *pamh,
486 void *data,
487 int error_status)
489 int ctrl = _pam_parse(pamh, 0, 0, NULL, NULL);
490 if (_pam_log_is_debug_state_enabled(ctrl)) {
491 __pam_log_debug(pamh, ctrl, LOG_DEBUG,
492 "[pamh: %p] CLEAN: cleaning up PAM data %p "
493 "(error_status = %d)", pamh, data,
494 error_status);
496 SAFE_FREE(data);
500 static const struct ntstatus_errors {
501 const char *ntstatus_string;
502 const char *error_string;
503 } ntstatus_errors[] = {
504 {"NT_STATUS_OK",
505 "Success"},
506 {"NT_STATUS_BACKUP_CONTROLLER",
507 "No primary Domain Controler available"},
508 {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND",
509 "No domain controllers found"},
510 {"NT_STATUS_NO_LOGON_SERVERS",
511 "No logon servers"},
512 {"NT_STATUS_PWD_TOO_SHORT",
513 "Password too short"},
514 {"NT_STATUS_PWD_TOO_RECENT",
515 "The password of this user is too recent to change"},
516 {"NT_STATUS_PWD_HISTORY_CONFLICT",
517 "Password is already in password history"},
518 {"NT_STATUS_PASSWORD_EXPIRED",
519 "Your password has expired"},
520 {"NT_STATUS_PASSWORD_MUST_CHANGE",
521 "You need to change your password now"},
522 {"NT_STATUS_INVALID_WORKSTATION",
523 "You are not allowed to logon from this workstation"},
524 {"NT_STATUS_INVALID_LOGON_HOURS",
525 "You are not allowed to logon at this time"},
526 {"NT_STATUS_ACCOUNT_EXPIRED",
527 "Your account has expired. "
528 "Please contact your System administrator"}, /* SCNR */
529 {"NT_STATUS_ACCOUNT_DISABLED",
530 "Your account is disabled. "
531 "Please contact your System administrator"}, /* SCNR */
532 {"NT_STATUS_ACCOUNT_LOCKED_OUT",
533 "Your account has been locked. "
534 "Please contact your System administrator"}, /* SCNR */
535 {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
536 "Invalid Trust Account"},
537 {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT",
538 "Invalid Trust Account"},
539 {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT",
540 "Invalid Trust Account"},
541 {"NT_STATUS_ACCESS_DENIED",
542 "Access is denied"},
543 {NULL, NULL}
546 static const char *_get_ntstatus_error_string(const char *nt_status_string)
548 int i;
549 for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) {
550 if (!strcasecmp(ntstatus_errors[i].ntstatus_string,
551 nt_status_string)) {
552 return ntstatus_errors[i].error_string;
555 return NULL;
558 /* --- authentication management functions --- */
560 /* Attempt a conversation */
562 static int converse(const pam_handle_t *pamh,
563 int nargs,
564 struct pam_message **message,
565 struct pam_response **response)
567 int retval;
568 struct pam_conv *conv;
570 retval = _pam_get_item(pamh, PAM_CONV, &conv);
571 if (retval == PAM_SUCCESS) {
572 retval = conv->conv(nargs,
573 (const struct pam_message **)message,
574 response, conv->appdata_ptr);
577 return retval; /* propagate error status */
581 static int _make_remark(struct pwb_context *ctx,
582 int type,
583 const char *text)
585 int retval = PAM_SUCCESS;
587 struct pam_message *pmsg[1], msg[1];
588 struct pam_response *resp;
590 if (ctx->flags & WINBIND_SILENT) {
591 return PAM_SUCCESS;
594 pmsg[0] = &msg[0];
595 msg[0].msg = discard_const_p(char, text);
596 msg[0].msg_style = type;
598 resp = NULL;
599 retval = converse(ctx->pamh, 1, pmsg, &resp);
601 if (resp) {
602 _pam_drop_reply(resp, 1);
604 return retval;
607 static int _make_remark_v(struct pwb_context *ctx,
608 int type,
609 const char *format,
610 va_list args)
612 char *var;
613 int ret;
615 ret = vasprintf(&var, format, args);
616 if (ret < 0) {
617 _pam_log(ctx, LOG_ERR, "memory allocation failure");
618 return ret;
621 ret = _make_remark(ctx, type, var);
622 SAFE_FREE(var);
623 return ret;
626 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
627 static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...)
629 int ret;
630 va_list args;
632 va_start(args, format);
633 ret = _make_remark_v(ctx, type, format, args);
634 va_end(args);
635 return ret;
638 static int pam_winbind_request(struct pwb_context *ctx,
639 enum winbindd_cmd req_type,
640 struct winbindd_request *request,
641 struct winbindd_response *response)
643 /* Fill in request and send down pipe */
644 winbindd_init_request(request, req_type);
646 if (winbind_write_sock(request, sizeof(*request), 0, 0) == -1) {
647 _pam_log(ctx, LOG_ERR,
648 "pam_winbind_request: write to socket failed!");
649 winbind_close_sock();
650 return PAM_SERVICE_ERR;
653 /* Wait for reply */
654 if (winbindd_read_reply(response) == -1) {
655 _pam_log(ctx, LOG_ERR,
656 "pam_winbind_request: read from socket failed!");
657 winbind_close_sock();
658 return PAM_SERVICE_ERR;
661 /* We are done with the socket - close it and avoid mischeif */
662 winbind_close_sock();
664 /* Copy reply data from socket */
665 if (response->result == WINBINDD_OK) {
666 return PAM_SUCCESS;
669 /* no need to check for pam_error codes for getpwnam() */
670 switch (req_type) {
672 case WINBINDD_GETPWNAM:
673 case WINBINDD_LOOKUPNAME:
674 if (strlen(response->data.auth.nt_status_string) > 0) {
675 _pam_log(ctx, LOG_ERR,
676 "request failed, NT error was %s",
677 response->data.auth.nt_status_string);
678 } else {
679 _pam_log(ctx, LOG_ERR, "request failed");
681 return PAM_USER_UNKNOWN;
682 default:
683 break;
686 if (response->data.auth.pam_error != PAM_SUCCESS) {
687 _pam_log(ctx, LOG_ERR,
688 "request failed: %s, "
689 "PAM error was %s (%d), NT error was %s",
690 response->data.auth.error_string,
691 pam_strerror(ctx->pamh, response->data.auth.pam_error),
692 response->data.auth.pam_error,
693 response->data.auth.nt_status_string);
694 return response->data.auth.pam_error;
697 _pam_log(ctx, LOG_ERR, "request failed, but PAM error 0!");
699 return PAM_SERVICE_ERR;
702 static int pam_winbind_request_log(struct pwb_context *ctx,
703 enum winbindd_cmd req_type,
704 struct winbindd_request *request,
705 struct winbindd_response *response,
706 const char *user)
708 int retval;
710 retval = pam_winbind_request(ctx, req_type, request, response);
712 switch (retval) {
713 case PAM_AUTH_ERR:
714 /* incorrect password */
715 _pam_log(ctx, LOG_WARNING, "user '%s' denied access "
716 "(incorrect password or invalid membership)", user);
717 return retval;
718 case PAM_ACCT_EXPIRED:
719 /* account expired */
720 _pam_log(ctx, LOG_WARNING, "user '%s' account expired",
721 user);
722 return retval;
723 case PAM_AUTHTOK_EXPIRED:
724 /* password expired */
725 _pam_log(ctx, LOG_WARNING, "user '%s' password expired",
726 user);
727 return retval;
728 case PAM_NEW_AUTHTOK_REQD:
729 /* new password required */
730 _pam_log(ctx, LOG_WARNING, "user '%s' new password "
731 "required", user);
732 return retval;
733 case PAM_USER_UNKNOWN:
734 /* the user does not exist */
735 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
736 user);
737 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
738 return PAM_IGNORE;
740 return retval;
741 case PAM_SUCCESS:
742 /* Otherwise, the authentication looked good */
743 switch (req_type) {
744 case WINBINDD_INFO:
745 break;
746 case WINBINDD_PAM_AUTH:
747 _pam_log(ctx, LOG_NOTICE,
748 "user '%s' granted access", user);
749 break;
750 case WINBINDD_PAM_CHAUTHTOK:
751 _pam_log(ctx, LOG_NOTICE,
752 "user '%s' password changed", user);
753 break;
754 default:
755 _pam_log(ctx, LOG_NOTICE,
756 "user '%s' OK", user);
757 break;
760 return retval;
761 default:
762 /* we don't know anything about this return value */
763 _pam_log(ctx, LOG_ERR,
764 "internal module error (retval = %d, user = '%s')",
765 retval, user);
766 return retval;
771 * send a password expiry message if required
773 * @param pamh PAM handle
774 * @param ctrl PAM winbind options.
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 pamh PAM handle
836 * @param ctrl PAM winbind options.
837 * @param response The full authentication response structure.
838 * @param already_expired boolean, is the pwd already expired?
840 * @return void.
843 static void _pam_warn_password_expiry(struct pwb_context *ctx,
844 const struct winbindd_response *response,
845 int warn_pwd_expire,
846 bool *already_expired)
848 time_t now = time(NULL);
849 time_t next_change = 0;
851 if (already_expired) {
852 *already_expired = false;
855 /* accounts with ACB_PWNOEXP set never receive a warning */
856 if (response->data.auth.info3.acct_flags & ACB_PWNOEXP) {
857 return;
860 /* no point in sending a warning if this is a grace logon */
861 if (PAM_WB_GRACE_LOGON(response->data.auth.info3.user_flgs)) {
862 return;
865 /* check if the info3 must change timestamp has been set */
866 next_change = response->data.auth.info3.pass_must_change_time;
868 if (_pam_send_password_expiry_message(ctx, next_change, now,
869 warn_pwd_expire,
870 already_expired)) {
871 return;
874 /* now check for the global password policy */
875 /* good catch from Ralf Haferkamp: an expiry of "never" is translated
876 * to -1 */
877 if (response->data.auth.policy.expire <= 0) {
878 return;
881 next_change = response->data.auth.info3.pass_last_set_time +
882 response->data.auth.policy.expire;
884 if (_pam_send_password_expiry_message(ctx, next_change, now,
885 warn_pwd_expire,
886 already_expired)) {
887 return;
890 /* no warning sent */
893 #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0)
896 * Append a string, making sure not to overflow and to always return a
897 * NULL-terminated string.
899 * @param dest Destination string buffer (must already be NULL-terminated).
900 * @param src Source string buffer.
901 * @param dest_buffer_size Size of dest buffer in bytes.
903 * @return false if dest buffer is not big enough (no bytes copied), true on
904 * success.
907 static bool safe_append_string(char *dest,
908 const char *src,
909 int dest_buffer_size)
911 int dest_length = strlen(dest);
912 int src_length = strlen(src);
914 if (dest_length + src_length + 1 > dest_buffer_size) {
915 return false;
918 memcpy(dest + dest_length, src, src_length + 1);
919 return true;
923 * Convert a names into a SID string, appending it to a buffer.
925 * @param pamh PAM handle
926 * @param ctrl PAM winbind options.
927 * @param user User in PAM request.
928 * @param name Name to convert.
929 * @param sid_list_buffer Where to append the string sid.
930 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
932 * @return false on failure, true on success.
934 static bool winbind_name_to_sid_string(struct pwb_context *ctx,
935 const char *user,
936 const char *name,
937 char *sid_list_buffer,
938 int sid_list_buffer_size)
940 const char* sid_string;
941 struct winbindd_response sid_response;
943 /* lookup name? */
944 if (IS_SID_STRING(name)) {
945 sid_string = name;
946 } else {
947 struct winbindd_request sid_request;
949 ZERO_STRUCT(sid_request);
950 ZERO_STRUCT(sid_response);
952 _pam_log_debug(ctx, LOG_DEBUG,
953 "no sid given, looking up: %s\n", name);
955 /* fortunatly winbindd can handle non-separated names */
956 strncpy(sid_request.data.name.name, name,
957 sizeof(sid_request.data.name.name) - 1);
959 if (pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
960 &sid_request, &sid_response,
961 user)) {
962 _pam_log(ctx, LOG_INFO,
963 "could not lookup name: %s\n", name);
964 return false;
967 sid_string = sid_response.data.sid.sid;
970 if (!safe_append_string(sid_list_buffer, sid_string,
971 sid_list_buffer_size)) {
972 return false;
975 return true;
979 * Convert a list of names into a list of sids.
981 * @param pamh PAM handle
982 * @param ctrl PAM winbind options.
983 * @param user User in PAM request.
984 * @param name_list List of names or string sids, separated by commas.
985 * @param sid_list_buffer Where to put the list of string sids.
986 * @param sid_list_buffer Size of sid_list_buffer (in bytes).
988 * @return false on failure, true on success.
990 static bool winbind_name_list_to_sid_string_list(struct pwb_context *ctx,
991 const char *user,
992 const char *name_list,
993 char *sid_list_buffer,
994 int sid_list_buffer_size)
996 bool result = false;
997 char *current_name = NULL;
998 const char *search_location;
999 const char *comma;
1001 if (sid_list_buffer_size > 0) {
1002 sid_list_buffer[0] = 0;
1005 search_location = name_list;
1006 while ((comma = strstr(search_location, ",")) != NULL) {
1007 current_name = strndup(search_location,
1008 comma - search_location);
1009 if (NULL == current_name) {
1010 goto out;
1013 if (!winbind_name_to_sid_string(ctx, user,
1014 current_name,
1015 sid_list_buffer,
1016 sid_list_buffer_size)) {
1017 goto out;
1020 SAFE_FREE(current_name);
1022 if (!safe_append_string(sid_list_buffer, ",",
1023 sid_list_buffer_size)) {
1024 goto out;
1027 search_location = comma + 1;
1030 if (!winbind_name_to_sid_string(ctx, user, search_location,
1031 sid_list_buffer,
1032 sid_list_buffer_size)) {
1033 goto out;
1036 result = true;
1038 out:
1039 SAFE_FREE(current_name);
1040 return result;
1044 * put krb5ccname variable into environment
1046 * @param pamh PAM handle
1047 * @param ctrl PAM winbind options.
1048 * @param krb5ccname env variable retrieved from winbindd.
1050 * @return void.
1053 static void _pam_setup_krb5_env(struct pwb_context *ctx,
1054 const char *krb5ccname)
1056 char var[PATH_MAX];
1057 int ret;
1059 if (off(ctx->ctrl, WINBIND_KRB5_AUTH)) {
1060 return;
1063 if (!krb5ccname || (strlen(krb5ccname) == 0)) {
1064 return;
1067 _pam_log_debug(ctx, LOG_DEBUG,
1068 "request returned KRB5CCNAME: %s", krb5ccname);
1070 if (snprintf(var, sizeof(var), "KRB5CCNAME=%s", krb5ccname) == -1) {
1071 return;
1074 ret = pam_putenv(ctx->pamh, var);
1075 if (ret) {
1076 _pam_log(ctx, LOG_ERR,
1077 "failed to set KRB5CCNAME to %s: %s",
1078 var, pam_strerror(ctx->pamh, ret));
1083 * Set string into the PAM stack.
1085 * @param pamh PAM handle
1086 * @param ctrl PAM winbind options.
1087 * @param data_name Key name for pam_set_data.
1088 * @param value String value.
1090 * @return void.
1093 static void _pam_set_data_string(struct pwb_context *ctx,
1094 const char *data_name,
1095 const char *value)
1097 int ret;
1099 if (!data_name || !value || (strlen(data_name) == 0) ||
1100 (strlen(value) == 0)) {
1101 return;
1104 ret = pam_set_data(ctx->pamh, data_name, (void *)strdup(value),
1105 _pam_winbind_cleanup_func);
1106 if (ret) {
1107 _pam_log_debug(ctx, LOG_DEBUG,
1108 "Could not set data %s: %s\n",
1109 data_name, pam_strerror(ctx->pamh, ret));
1115 * Set info3 strings into the PAM stack.
1117 * @param pamh PAM handle
1118 * @param ctrl PAM winbind options.
1119 * @param data_name Key name for pam_set_data.
1120 * @param value String value.
1122 * @return void.
1125 static void _pam_set_data_info3(struct pwb_context *ctx,
1126 struct winbindd_response *response)
1128 _pam_set_data_string(ctx, PAM_WINBIND_HOMEDIR,
1129 response->data.auth.info3.home_dir);
1130 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSCRIPT,
1131 response->data.auth.info3.logon_script);
1132 _pam_set_data_string(ctx, PAM_WINBIND_LOGONSERVER,
1133 response->data.auth.info3.logon_srv);
1134 _pam_set_data_string(ctx, PAM_WINBIND_PROFILEPATH,
1135 response->data.auth.info3.profile_path);
1139 * Free info3 strings in the PAM stack.
1141 * @param pamh PAM handle
1143 * @return void.
1146 static void _pam_free_data_info3(pam_handle_t *pamh)
1148 pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL);
1149 pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL);
1150 pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL);
1151 pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL);
1155 * Send PAM_ERROR_MSG for cached or grace logons.
1157 * @param pamh PAM handle
1158 * @param ctrl PAM winbind options.
1159 * @param username User in PAM request.
1160 * @param info3_user_flgs Info3 flags containing logon type bits.
1162 * @return void.
1165 static void _pam_warn_logon_type(struct pwb_context *ctx,
1166 const char *username,
1167 uint32_t info3_user_flgs)
1169 /* inform about logon type */
1170 if (PAM_WB_GRACE_LOGON(info3_user_flgs)) {
1172 _make_remark(ctx, PAM_ERROR_MSG,
1173 "Grace login. "
1174 "Please change your password as soon you're "
1175 "online again");
1176 _pam_log_debug(ctx, LOG_DEBUG,
1177 "User %s logged on using grace logon\n",
1178 username);
1180 } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) {
1182 _make_remark(ctx, PAM_ERROR_MSG,
1183 "Domain Controller unreachable, "
1184 "using cached credentials instead. "
1185 "Network resources may be unavailable");
1186 _pam_log_debug(ctx, LOG_DEBUG,
1187 "User %s logged on using cached credentials\n",
1188 username);
1193 * Send PAM_ERROR_MSG for krb5 errors.
1195 * @param pamh PAM handle
1196 * @param ctrl PAM winbind options.
1197 * @param username User in PAM request.
1198 * @param info3_user_flgs Info3 flags containing logon type bits.
1200 * @return void.
1203 static void _pam_warn_krb5_failure(struct pwb_context *ctx,
1204 const char *username,
1205 uint32_t info3_user_flgs)
1207 if (PAM_WB_KRB5_CLOCK_SKEW(info3_user_flgs)) {
1208 _make_remark(ctx, PAM_ERROR_MSG,
1209 "Failed to establish your Kerberos Ticket cache "
1210 "due time differences\n"
1211 "with the domain controller. "
1212 "Please verify the system time.\n");
1213 _pam_log_debug(ctx, LOG_DEBUG,
1214 "User %s: Clock skew when getting Krb5 TGT\n",
1215 username);
1220 * Compose Password Restriction String for a PAM_ERROR_MSG conversation.
1222 * @param response The struct winbindd_response.
1224 * @return string (caller needs to free).
1227 static char *_pam_compose_pwd_restriction_string(struct winbindd_response *response)
1229 char *str = NULL;
1230 size_t offset = 0, ret = 0, str_size = 1024;
1232 str = (char *)malloc(str_size);
1233 if (!str) {
1234 return NULL;
1237 memset(str, '\0', str_size);
1239 offset = snprintf(str, str_size, "Your password ");
1240 if (offset == -1) {
1241 goto failed;
1244 if (response->data.auth.policy.min_length_password > 0) {
1245 ret = snprintf(str+offset, str_size-offset,
1246 "must be at least %d characters; ",
1247 response->data.auth.policy.min_length_password);
1248 if (ret == -1) {
1249 goto failed;
1251 offset += ret;
1254 if (response->data.auth.policy.password_history > 0) {
1255 ret = snprintf(str+offset, str_size-offset,
1256 "cannot repeat any of your previous %d "
1257 "passwords; ",
1258 response->data.auth.policy.password_history);
1259 if (ret == -1) {
1260 goto failed;
1262 offset += ret;
1265 if (response->data.auth.policy.password_properties &
1266 DOMAIN_PASSWORD_COMPLEX) {
1267 ret = snprintf(str+offset, str_size-offset,
1268 "must contain capitals, numerals "
1269 "or punctuation; "
1270 "and cannot contain your account "
1271 "or full name; ");
1272 if (ret == -1) {
1273 goto failed;
1275 offset += ret;
1278 ret = snprintf(str+offset, str_size-offset,
1279 "Please type a different password. "
1280 "Type a password which meets these requirements in "
1281 "both text boxes.");
1282 if (ret == -1) {
1283 goto failed;
1286 return str;
1288 failed:
1289 SAFE_FREE(str);
1290 return NULL;
1293 /* talk to winbindd */
1294 static int winbind_auth_request(struct pwb_context *ctx,
1295 const char *user,
1296 const char *pass,
1297 const char *member,
1298 const char *cctype,
1299 const int warn_pwd_expire,
1300 struct winbindd_response *p_response,
1301 time_t *pwd_last_set,
1302 char **user_ret)
1304 struct winbindd_request request;
1305 struct winbindd_response response;
1306 int ret;
1307 bool already_expired = false;
1309 ZERO_STRUCT(request);
1310 ZERO_STRUCT(response);
1312 if (pwd_last_set) {
1313 *pwd_last_set = 0;
1316 strncpy(request.data.auth.user, user,
1317 sizeof(request.data.auth.user)-1);
1319 strncpy(request.data.auth.pass, pass,
1320 sizeof(request.data.auth.pass)-1);
1322 request.data.auth.krb5_cc_type[0] = '\0';
1323 request.data.auth.uid = -1;
1325 request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_GET_PWD_POLICY;
1327 /* Krb5 auth always has to go against the KDC of the user's realm */
1329 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1330 request.flags |= WBFLAG_PAM_CONTACT_TRUSTDOM;
1333 if (ctx->ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) {
1334 struct passwd *pwd = NULL;
1336 pwd = getpwnam(user);
1337 if (pwd == NULL) {
1338 return PAM_USER_UNKNOWN;
1340 request.data.auth.uid = pwd->pw_uid;
1343 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1345 _pam_log_debug(ctx, LOG_DEBUG,
1346 "enabling krb5 login flag\n");
1348 request.flags |= WBFLAG_PAM_KRB5 |
1349 WBFLAG_PAM_FALLBACK_AFTER_KRB5;
1352 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1353 _pam_log_debug(ctx, LOG_DEBUG,
1354 "enabling cached login flag\n");
1355 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1358 if (user_ret) {
1359 *user_ret = NULL;
1360 request.flags |= WBFLAG_PAM_UNIX_NAME;
1363 if (cctype != NULL) {
1364 strncpy(request.data.auth.krb5_cc_type, cctype,
1365 sizeof(request.data.auth.krb5_cc_type) - 1);
1366 _pam_log_debug(ctx, LOG_DEBUG,
1367 "enabling request for a %s krb5 ccache\n",
1368 cctype);
1371 request.data.auth.require_membership_of_sid[0] = '\0';
1373 if (member != NULL) {
1375 if (!winbind_name_list_to_sid_string_list(ctx, user,
1376 member,
1377 request.data.auth.require_membership_of_sid,
1378 sizeof(request.data.auth.require_membership_of_sid))) {
1380 _pam_log_debug(ctx, LOG_ERR,
1381 "failed to serialize membership of sid "
1382 "\"%s\"\n", member);
1383 return PAM_AUTH_ERR;
1387 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_AUTH,
1388 &request, &response, user);
1390 if (pwd_last_set) {
1391 *pwd_last_set = response.data.auth.info3.pass_last_set_time;
1394 if (p_response) {
1395 /* We want to process the response in the caller. */
1396 *p_response = response;
1397 return ret;
1400 if (ret) {
1401 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1402 "NT_STATUS_PASSWORD_EXPIRED");
1403 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1404 "NT_STATUS_PASSWORD_MUST_CHANGE");
1405 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1406 "NT_STATUS_INVALID_WORKSTATION");
1407 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1408 "NT_STATUS_INVALID_LOGON_HOURS");
1409 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1410 "NT_STATUS_ACCOUNT_EXPIRED");
1411 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1412 "NT_STATUS_ACCOUNT_DISABLED");
1413 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1414 "NT_STATUS_ACCOUNT_LOCKED_OUT");
1415 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1416 "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT");
1417 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1418 "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT");
1419 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1420 "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT");
1421 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1422 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1423 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1424 "NT_STATUS_NO_LOGON_SERVERS");
1425 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1426 "NT_STATUS_WRONG_PASSWORD");
1427 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1428 "NT_STATUS_ACCESS_DENIED");
1431 if (ret == PAM_SUCCESS) {
1433 /* warn a user if the password is about to expire soon */
1434 _pam_warn_password_expiry(ctx, &response,
1435 warn_pwd_expire,
1436 &already_expired);
1438 if (already_expired == true) {
1439 SMB_TIME_T last_set;
1440 last_set = response.data.auth.info3.pass_last_set_time;
1441 _pam_log_debug(ctx, LOG_DEBUG,
1442 "Password has expired "
1443 "(Password was last set: %lld, "
1444 "the policy says it should expire here "
1445 "%lld (now it's: %lu))\n",
1446 (long long int)last_set,
1447 (long long int)last_set +
1448 response.data.auth.policy.expire,
1449 time(NULL));
1451 return PAM_AUTHTOK_EXPIRED;
1454 /* inform about logon type */
1455 _pam_warn_logon_type(ctx, user,
1456 response.data.auth.info3.user_flgs);
1458 /* inform about krb5 failures */
1459 _pam_warn_krb5_failure(ctx, user,
1460 response.data.auth.info3.user_flgs);
1462 /* set some info3 info for other modules in the stack */
1463 _pam_set_data_info3(ctx, &response);
1465 /* put krb5ccname into env */
1466 _pam_setup_krb5_env(ctx, response.data.auth.krb5ccname);
1468 /* If winbindd returned a username, return the pointer to it
1469 * here. */
1470 if (user_ret && response.data.auth.unix_username[0]) {
1471 /* We have to trust it's a null terminated string. */
1472 *user_ret = strndup(response.data.auth.unix_username,
1473 sizeof(response.data.auth.unix_username) - 1);
1477 return ret;
1480 /* talk to winbindd */
1481 static int winbind_chauthtok_request(struct pwb_context *ctx,
1482 const char *user,
1483 const char *oldpass,
1484 const char *newpass,
1485 time_t pwd_last_set)
1487 struct winbindd_request request;
1488 struct winbindd_response response;
1489 int ret;
1491 ZERO_STRUCT(request);
1492 ZERO_STRUCT(response);
1494 if (request.data.chauthtok.user == NULL) {
1495 return -2;
1498 strncpy(request.data.chauthtok.user, user,
1499 sizeof(request.data.chauthtok.user) - 1);
1501 if (oldpass != NULL) {
1502 strncpy(request.data.chauthtok.oldpass, oldpass,
1503 sizeof(request.data.chauthtok.oldpass) - 1);
1504 } else {
1505 request.data.chauthtok.oldpass[0] = '\0';
1508 if (newpass != NULL) {
1509 strncpy(request.data.chauthtok.newpass, newpass,
1510 sizeof(request.data.chauthtok.newpass) - 1);
1511 } else {
1512 request.data.chauthtok.newpass[0] = '\0';
1515 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
1516 request.flags = WBFLAG_PAM_KRB5 |
1517 WBFLAG_PAM_CONTACT_TRUSTDOM;
1520 if (ctx->ctrl & WINBIND_CACHED_LOGIN) {
1521 request.flags |= WBFLAG_PAM_CACHED_LOGIN;
1524 ret = pam_winbind_request_log(ctx, WINBINDD_PAM_CHAUTHTOK,
1525 &request, &response, user);
1527 if (ret == PAM_SUCCESS) {
1528 return ret;
1531 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1532 "NT_STATUS_BACKUP_CONTROLLER");
1533 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1534 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
1535 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1536 "NT_STATUS_NO_LOGON_SERVERS");
1537 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1538 "NT_STATUS_ACCESS_DENIED");
1540 /* TODO: tell the min pwd length ? */
1541 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1542 "NT_STATUS_PWD_TOO_SHORT");
1544 /* TODO: tell the minage ? */
1545 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1546 "NT_STATUS_PWD_TOO_RECENT");
1548 /* TODO: tell the history length ? */
1549 PAM_WB_REMARK_CHECK_RESPONSE_RET(ctx, response,
1550 "NT_STATUS_PWD_HISTORY_CONFLICT");
1552 if (!strcasecmp(response.data.auth.nt_status_string,
1553 "NT_STATUS_PASSWORD_RESTRICTION")) {
1555 char *pwd_restriction_string = NULL;
1556 SMB_TIME_T min_pwd_age;
1557 uint32_t reject_reason = response.data.auth.reject_reason;
1558 min_pwd_age = response.data.auth.policy.min_passwordage;
1560 /* FIXME: avoid to send multiple PAM messages after another */
1561 switch (reject_reason) {
1562 case -1:
1563 break;
1564 case SAMR_REJECT_OTHER:
1565 if ((min_pwd_age > 0) &&
1566 (pwd_last_set + min_pwd_age > time(NULL))) {
1567 PAM_WB_REMARK_DIRECT(ctx,
1568 "NT_STATUS_PWD_TOO_RECENT");
1570 break;
1571 case SAMR_REJECT_TOO_SHORT:
1572 PAM_WB_REMARK_DIRECT(ctx,
1573 "NT_STATUS_PWD_TOO_SHORT");
1574 break;
1575 case SAMR_REJECT_IN_HISTORY:
1576 PAM_WB_REMARK_DIRECT(ctx,
1577 "NT_STATUS_PWD_HISTORY_CONFLICT");
1578 break;
1579 case SAMR_REJECT_COMPLEXITY:
1580 _make_remark(ctx, PAM_ERROR_MSG,
1581 "Password does not meet "
1582 "complexity requirements");
1583 break;
1584 default:
1585 _pam_log_debug(ctx, LOG_DEBUG,
1586 "unknown password change "
1587 "reject reason: %d",
1588 reject_reason);
1589 break;
1592 pwd_restriction_string =
1593 _pam_compose_pwd_restriction_string(&response);
1594 if (pwd_restriction_string) {
1595 _make_remark(ctx, PAM_ERROR_MSG,
1596 pwd_restriction_string);
1597 SAFE_FREE(pwd_restriction_string);
1601 return ret;
1605 * Checks if a user has an account
1607 * return values:
1608 * 1 = User not found
1609 * 0 = OK
1610 * -1 = System error
1612 static int valid_user(struct pwb_context *ctx,
1613 const char *user)
1615 /* check not only if the user is available over NSS calls, also make
1616 * sure it's really a winbind user, this is important when stacking PAM
1617 * modules in the 'account' or 'password' facility. */
1619 struct passwd *pwd = NULL;
1620 struct winbindd_request request;
1621 struct winbindd_response response;
1622 int ret;
1624 ZERO_STRUCT(request);
1625 ZERO_STRUCT(response);
1627 pwd = getpwnam(user);
1628 if (pwd == NULL) {
1629 return 1;
1632 strncpy(request.data.username, user,
1633 sizeof(request.data.username) - 1);
1635 ret = pam_winbind_request_log(ctx, WINBINDD_GETPWNAM,
1636 &request, &response, user);
1638 switch (ret) {
1639 case PAM_USER_UNKNOWN:
1640 return 1;
1641 case PAM_SUCCESS:
1642 return 0;
1643 default:
1644 break;
1646 return -1;
1649 static char *_pam_delete(register char *xx)
1651 _pam_overwrite(xx);
1652 _pam_drop(xx);
1653 return NULL;
1657 * obtain a password from the user
1660 static int _winbind_read_password(struct pwb_context *ctx,
1661 unsigned int ctrl,
1662 const char *comment,
1663 const char *prompt1,
1664 const char *prompt2,
1665 const char **pass)
1667 int authtok_flag;
1668 int retval;
1669 const char *item;
1670 char *token;
1672 _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl);
1675 * make sure nothing inappropriate gets returned
1678 *pass = token = NULL;
1681 * which authentication token are we getting?
1684 if (on(WINBIND__OLD_PASSWORD, ctrl)) {
1685 authtok_flag = PAM_OLDAUTHTOK;
1686 } else {
1687 authtok_flag = PAM_AUTHTOK;
1691 * should we obtain the password from a PAM item ?
1694 if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) ||
1695 on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1696 retval = _pam_get_item(ctx->pamh, authtok_flag, &item);
1697 if (retval != PAM_SUCCESS) {
1698 /* very strange. */
1699 _pam_log(ctx, LOG_ALERT,
1700 "pam_get_item returned error "
1701 "to unix-read-password");
1702 return retval;
1703 } else if (item != NULL) { /* we have a password! */
1704 *pass = item;
1705 item = NULL;
1706 _pam_log(ctx, LOG_DEBUG,
1707 "pam_get_item returned a password");
1708 return PAM_SUCCESS;
1709 } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) {
1710 return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
1711 } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl)
1712 && off(WINBIND__OLD_PASSWORD, ctrl)) {
1713 return PAM_AUTHTOK_RECOVER_ERR;
1717 * getting here implies we will have to get the password from the
1718 * user directly.
1722 struct pam_message msg[3], *pmsg[3];
1723 struct pam_response *resp;
1724 int i, replies;
1726 /* prepare to converse */
1728 if (comment != NULL && off(ctrl, WINBIND_SILENT)) {
1729 pmsg[0] = &msg[0];
1730 msg[0].msg_style = PAM_TEXT_INFO;
1731 msg[0].msg = discard_const_p(char, comment);
1732 i = 1;
1733 } else {
1734 i = 0;
1737 pmsg[i] = &msg[i];
1738 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1739 msg[i++].msg = discard_const_p(char, prompt1);
1740 replies = 1;
1742 if (prompt2 != NULL) {
1743 pmsg[i] = &msg[i];
1744 msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
1745 msg[i++].msg = discard_const_p(char, prompt2);
1746 ++replies;
1748 /* so call the conversation expecting i responses */
1749 resp = NULL;
1750 retval = converse(ctx->pamh, i, pmsg, &resp);
1751 if (resp == NULL) {
1752 if (retval == PAM_SUCCESS) {
1753 retval = PAM_AUTHTOK_RECOVER_ERR;
1755 goto done;
1757 if (retval != PAM_SUCCESS) {
1758 _pam_drop_reply(resp, i);
1759 goto done;
1762 /* interpret the response */
1764 token = x_strdup(resp[i - replies].resp);
1765 if (!token) {
1766 _pam_log(ctx, LOG_NOTICE,
1767 "could not recover "
1768 "authentication token");
1769 retval = PAM_AUTHTOK_RECOVER_ERR;
1770 goto done;
1773 if (replies == 2) {
1774 /* verify that password entered correctly */
1775 if (!resp[i - 1].resp ||
1776 strcmp(token, resp[i - 1].resp)) {
1777 _pam_delete(token); /* mistyped */
1778 retval = PAM_AUTHTOK_RECOVER_ERR;
1779 _make_remark(ctx, PAM_ERROR_MSG,
1780 MISTYPED_PASS);
1785 * tidy up the conversation (resp_retcode) is ignored
1786 * -- what is it for anyway? AGM
1788 _pam_drop_reply(resp, i);
1791 done:
1792 if (retval != PAM_SUCCESS) {
1793 _pam_log_debug(ctx, LOG_DEBUG,
1794 "unable to obtain a password");
1795 return retval;
1797 /* 'token' is the entered password */
1799 /* we store this password as an item */
1801 retval = pam_set_item(ctx->pamh, authtok_flag, token);
1802 _pam_delete(token); /* clean it up */
1803 if (retval != PAM_SUCCESS ||
1804 (retval = _pam_get_item(ctx->pamh, authtok_flag, &item)) != PAM_SUCCESS) {
1806 _pam_log(ctx, LOG_CRIT, "error manipulating password");
1807 return retval;
1811 *pass = item;
1812 item = NULL; /* break link to password */
1814 return PAM_SUCCESS;
1817 static const char *get_conf_item_string(struct pwb_context *ctx,
1818 const char *item,
1819 int config_flag)
1821 int i = 0;
1822 const char *parm_opt = NULL;
1824 if (!(ctx->ctrl & config_flag)) {
1825 goto out;
1828 /* let the pam opt take precedence over the pam_winbind.conf option */
1829 for (i=0; i<ctx->argc; i++) {
1831 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1832 char *p;
1834 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1835 _pam_log(ctx, LOG_INFO,
1836 "no \"=\" delimiter for \"%s\" found\n",
1837 item);
1838 goto out;
1840 _pam_log_debug(ctx, LOG_INFO,
1841 "PAM config: %s '%s'\n", item, p+1);
1842 return p + 1;
1846 if (ctx->dict) {
1847 char *key = NULL;
1849 if (!asprintf(&key, "global:%s", item)) {
1850 goto out;
1853 parm_opt = iniparser_getstr(ctx->dict, key);
1854 SAFE_FREE(key);
1856 _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n",
1857 item, parm_opt);
1859 out:
1860 return parm_opt;
1863 static int get_config_item_int(struct pwb_context *ctx,
1864 const char *item,
1865 int config_flag)
1867 int i, parm_opt = -1;
1869 if (!(ctx->ctrl & config_flag)) {
1870 goto out;
1873 /* let the pam opt take precedence over the pam_winbind.conf option */
1874 for (i = 0; i < ctx->argc; i++) {
1876 if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) {
1877 char *p;
1879 if ((p = strchr(ctx->argv[i], '=')) == NULL) {
1880 _pam_log(ctx, LOG_INFO,
1881 "no \"=\" delimiter for \"%s\" found\n",
1882 item);
1883 goto out;
1885 parm_opt = atoi(p + 1);
1886 _pam_log_debug(ctx, LOG_INFO,
1887 "PAM config: %s '%d'\n",
1888 item, parm_opt);
1889 return parm_opt;
1893 if (ctx->dict) {
1894 char *key = NULL;
1896 if (!asprintf(&key, "global:%s", item)) {
1897 goto out;
1900 parm_opt = iniparser_getint(ctx->dict, key, -1);
1901 SAFE_FREE(key);
1903 _pam_log_debug(ctx, LOG_INFO,
1904 "CONFIG file: %s '%d'\n",
1905 item, parm_opt);
1907 out:
1908 return parm_opt;
1911 static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx)
1913 return get_conf_item_string(ctx, "krb5_ccache_type",
1914 WINBIND_KRB5_CCACHE_TYPE);
1917 static const char *get_member_from_config(struct pwb_context *ctx)
1919 const char *ret = NULL;
1920 ret = get_conf_item_string(ctx, "require_membership_of",
1921 WINBIND_REQUIRED_MEMBERSHIP);
1922 if (ret) {
1923 return ret;
1925 return get_conf_item_string(ctx, "require-membership-of",
1926 WINBIND_REQUIRED_MEMBERSHIP);
1929 static int get_warn_pwd_expire_from_config(struct pwb_context *ctx)
1931 int ret;
1932 ret = get_config_item_int(ctx, "warn_pwd_expire",
1933 WINBIND_WARN_PWD_EXPIRE);
1934 /* no or broken setting */
1935 if (ret <= 0) {
1936 return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES;
1938 return ret;
1942 * Retrieve the winbind separator.
1944 * @param pamh PAM handle
1945 * @param ctrl PAM winbind options.
1947 * @return string separator character. NULL on failure.
1950 static char winbind_get_separator(struct pwb_context *ctx)
1952 struct winbindd_request request;
1953 struct winbindd_response response;
1955 ZERO_STRUCT(request);
1956 ZERO_STRUCT(response);
1958 if (pam_winbind_request_log(ctx, WINBINDD_INFO,
1959 &request, &response, NULL)) {
1960 return '\0';
1963 return response.data.info.winbind_separator;
1967 * Convert a upn to a name.
1969 * @param pamh PAM handle
1970 * @param ctrl PAM winbind options.
1971 * @param upn USer UPN to be trabslated.
1973 * @return converted name. NULL pointer on failure. Caller needs to free.
1976 static char* winbind_upn_to_username(struct pwb_context *ctx,
1977 const char *upn)
1979 struct winbindd_request req;
1980 struct winbindd_response resp;
1981 int retval;
1982 char *account_name;
1983 int account_name_len;
1984 char sep;
1986 /* This cannot work when the winbind separator = @ */
1988 sep = winbind_get_separator(ctx);
1989 if (!sep || sep == '@') {
1990 return NULL;
1993 /* Convert the UPN to a SID */
1995 ZERO_STRUCT(req);
1996 ZERO_STRUCT(resp);
1998 strncpy(req.data.name.dom_name, "",
1999 sizeof(req.data.name.dom_name) - 1);
2000 strncpy(req.data.name.name, upn,
2001 sizeof(req.data.name.name) - 1);
2002 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPNAME,
2003 &req, &resp, upn);
2004 if (retval != PAM_SUCCESS) {
2005 return NULL;
2008 /* Convert the the SID back to the sAMAccountName */
2010 ZERO_STRUCT(req);
2011 strncpy(req.data.sid, resp.data.sid.sid, sizeof(req.data.sid)-1);
2012 ZERO_STRUCT(resp);
2013 retval = pam_winbind_request_log(ctx, WINBINDD_LOOKUPSID,
2014 &req, &resp, upn);
2015 if (retval != PAM_SUCCESS) {
2016 return NULL;
2019 account_name_len = asprintf(&account_name, "%s\\%s",
2020 resp.data.name.dom_name,
2021 resp.data.name.name);
2023 return account_name;
2026 PAM_EXTERN
2027 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
2028 int argc, const char **argv)
2030 const char *username;
2031 const char *password;
2032 const char *member = NULL;
2033 const char *cctype = NULL;
2034 int warn_pwd_expire;
2035 int retval = PAM_AUTH_ERR;
2036 char *username_ret = NULL;
2037 char *new_authtok_required = NULL;
2038 char *real_username = NULL;
2039 struct pwb_context *ctx = NULL;
2041 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2042 if (retval) {
2043 goto out;
2046 _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx);
2048 /* Get the username */
2049 retval = pam_get_user(pamh, &username, NULL);
2050 if ((retval != PAM_SUCCESS) || (!username)) {
2051 _pam_log_debug(ctx, LOG_DEBUG,
2052 "can not get the username");
2053 retval = PAM_SERVICE_ERR;
2054 goto out;
2058 #if defined(AIX)
2059 /* Decode the user name since AIX does not support logn user
2060 names by default. The name is encoded as _#uid. */
2062 if (username[0] == '_') {
2063 uid_t id = atoi(&username[1]);
2064 struct passwd *pw = NULL;
2066 if ((id!=0) && ((pw = getpwuid(id)) != NULL)) {
2067 real_username = strdup(pw->pw_name);
2070 #endif
2072 if (!real_username) {
2073 /* Just making a copy of the username we got from PAM */
2074 if ((real_username = strdup(username)) == NULL) {
2075 _pam_log_debug(ctx, LOG_DEBUG,
2076 "memory allocation failure when copying "
2077 "username");
2078 retval = PAM_SERVICE_ERR;
2079 goto out;
2083 /* Maybe this was a UPN */
2085 if (strchr(real_username, '@') != NULL) {
2086 char *samaccountname = NULL;
2088 samaccountname = winbind_upn_to_username(ctx,
2089 real_username);
2090 if (samaccountname) {
2091 free(real_username);
2092 real_username = samaccountname;
2096 retval = _winbind_read_password(ctx, ctx->ctrl, NULL,
2097 "Password: ", NULL,
2098 &password);
2100 if (retval != PAM_SUCCESS) {
2101 _pam_log(ctx, LOG_ERR,
2102 "Could not retrieve user's password");
2103 retval = PAM_AUTHTOK_ERR;
2104 goto out;
2107 /* Let's not give too much away in the log file */
2109 #ifdef DEBUG_PASSWORD
2110 _pam_log_debug(ctx, LOG_INFO,
2111 "Verify user '%s' with password '%s'",
2112 real_username, password);
2113 #else
2114 _pam_log_debug(ctx, LOG_INFO,
2115 "Verify user '%s'", real_username);
2116 #endif
2118 member = get_member_from_config(ctx);
2119 cctype = get_krb5_cc_type_from_config(ctx);
2120 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2122 /* Now use the username to look up password */
2123 retval = winbind_auth_request(ctx, real_username, password,
2124 member, cctype, warn_pwd_expire, NULL,
2125 NULL, &username_ret);
2127 if (retval == PAM_NEW_AUTHTOK_REQD ||
2128 retval == PAM_AUTHTOK_EXPIRED) {
2130 char *new_authtok_required_during_auth = NULL;
2132 if (!asprintf(&new_authtok_required, "%d", retval)) {
2133 retval = PAM_BUF_ERR;
2134 goto out;
2137 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2138 new_authtok_required,
2139 _pam_winbind_cleanup_func);
2141 retval = PAM_SUCCESS;
2143 if (!asprintf(&new_authtok_required_during_auth, "%d", true)) {
2144 retval = PAM_BUF_ERR;
2145 goto out;
2148 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2149 new_authtok_required_during_auth,
2150 _pam_winbind_cleanup_func);
2152 goto out;
2155 out:
2156 if (username_ret) {
2157 pam_set_item (pamh, PAM_USER, username_ret);
2158 _pam_log_debug(ctx, LOG_INFO,
2159 "Returned user was '%s'", username_ret);
2160 free(username_ret);
2163 if (real_username) {
2164 free(real_username);
2167 if (!new_authtok_required) {
2168 pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL);
2171 if (retval != PAM_SUCCESS) {
2172 _pam_free_data_info3(pamh);
2175 _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
2177 _pam_winbind_free_context(ctx);
2179 return retval;
2182 PAM_EXTERN
2183 int pam_sm_setcred(pam_handle_t *pamh, int flags,
2184 int argc, const char **argv)
2186 int ret = PAM_SYSTEM_ERR;
2187 struct pwb_context *ctx = NULL;
2189 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2190 if (ret) {
2191 goto out;
2194 _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx);
2196 switch (flags & ~PAM_SILENT) {
2198 case PAM_DELETE_CRED:
2199 ret = pam_sm_close_session(pamh, flags, argc, argv);
2200 break;
2201 case PAM_REFRESH_CRED:
2202 _pam_log_debug(ctx, LOG_WARNING,
2203 "PAM_REFRESH_CRED not implemented");
2204 ret = PAM_SUCCESS;
2205 break;
2206 case PAM_REINITIALIZE_CRED:
2207 _pam_log_debug(ctx, LOG_WARNING,
2208 "PAM_REINITIALIZE_CRED not implemented");
2209 ret = PAM_SUCCESS;
2210 break;
2211 case PAM_ESTABLISH_CRED:
2212 _pam_log_debug(ctx, LOG_WARNING,
2213 "PAM_ESTABLISH_CRED not implemented");
2214 ret = PAM_SUCCESS;
2215 break;
2216 default:
2217 ret = PAM_SYSTEM_ERR;
2218 break;
2221 out:
2223 _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret);
2225 _pam_winbind_free_context(ctx);
2227 return ret;
2231 * Account management. We want to verify that the account exists
2232 * before returning PAM_SUCCESS
2234 PAM_EXTERN
2235 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
2236 int argc, const char **argv)
2238 const char *username;
2239 int ret = PAM_USER_UNKNOWN;
2240 void *tmp = NULL;
2241 struct pwb_context *ctx = NULL;
2243 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2244 if (ret) {
2245 goto out;
2248 _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx);
2251 /* Get the username */
2252 ret = pam_get_user(pamh, &username, NULL);
2253 if ((ret != PAM_SUCCESS) || (!username)) {
2254 _pam_log_debug(ctx, LOG_DEBUG,
2255 "can not get the username");
2256 ret = PAM_SERVICE_ERR;
2257 goto out;
2260 /* Verify the username */
2261 ret = valid_user(ctx, username);
2262 switch (ret) {
2263 case -1:
2264 /* some sort of system error. The log was already printed */
2265 ret = PAM_SERVICE_ERR;
2266 goto out;
2267 case 1:
2268 /* the user does not exist */
2269 _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found",
2270 username);
2271 if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) {
2272 ret = PAM_IGNORE;
2273 goto out;
2275 ret = PAM_USER_UNKNOWN;
2276 goto out;
2277 case 0:
2278 pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD,
2279 (const void **)&tmp);
2280 if (tmp != NULL) {
2281 ret = atoi((const char *)tmp);
2282 switch (ret) {
2283 case PAM_AUTHTOK_EXPIRED:
2284 /* fall through, since new token is required in this case */
2285 case PAM_NEW_AUTHTOK_REQD:
2286 _pam_log(ctx, LOG_WARNING,
2287 "pam_sm_acct_mgmt success but %s is set",
2288 PAM_WINBIND_NEW_AUTHTOK_REQD);
2289 _pam_log(ctx, LOG_NOTICE,
2290 "user '%s' needs new password",
2291 username);
2292 /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
2293 ret = PAM_NEW_AUTHTOK_REQD;
2294 goto out;
2295 default:
2296 _pam_log(ctx, LOG_WARNING,
2297 "pam_sm_acct_mgmt success");
2298 _pam_log(ctx, LOG_NOTICE,
2299 "user '%s' granted access", username);
2300 ret = PAM_SUCCESS;
2301 goto out;
2305 /* Otherwise, the authentication looked good */
2306 _pam_log(ctx, LOG_NOTICE,
2307 "user '%s' granted access", username);
2308 ret = PAM_SUCCESS;
2309 goto out;
2310 default:
2311 /* we don't know anything about this return value */
2312 _pam_log(ctx, LOG_ERR,
2313 "internal module error (ret = %d, user = '%s')",
2314 ret, username);
2315 ret = PAM_SERVICE_ERR;
2316 goto out;
2319 /* should not be reached */
2320 ret = PAM_IGNORE;
2322 out:
2324 _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret);
2326 _pam_winbind_free_context(ctx);
2328 return ret;
2331 PAM_EXTERN
2332 int pam_sm_open_session(pam_handle_t *pamh, int flags,
2333 int argc, const char **argv)
2335 int ret = PAM_SYSTEM_ERR;
2336 struct pwb_context *ctx = NULL;
2338 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2339 if (ret) {
2340 goto out;
2343 _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx);
2345 ret = PAM_SUCCESS;
2347 out:
2348 _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret);
2350 _pam_winbind_free_context(ctx);
2352 return ret;
2355 PAM_EXTERN
2356 int pam_sm_close_session(pam_handle_t *pamh, int flags,
2357 int argc, const char **argv)
2359 int retval = PAM_SUCCESS;
2360 struct pwb_context *ctx = NULL;
2362 retval = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2363 if (retval) {
2364 goto out;
2367 _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx);
2369 if (!(flags & PAM_DELETE_CRED)) {
2370 retval = PAM_SUCCESS;
2371 goto out;
2374 if (ctx->ctrl & WINBIND_KRB5_AUTH) {
2376 /* destroy the ccache here */
2377 struct winbindd_request request;
2378 struct winbindd_response response;
2379 const char *user;
2380 const char *ccname = NULL;
2381 struct passwd *pwd = NULL;
2383 ZERO_STRUCT(request);
2384 ZERO_STRUCT(response);
2386 retval = pam_get_user(pamh, &user, "Username: ");
2387 if (retval) {
2388 _pam_log(ctx, LOG_ERR,
2389 "could not identify user");
2390 goto out;
2393 if (user == NULL) {
2394 _pam_log(ctx, LOG_ERR,
2395 "username was NULL!");
2396 retval = PAM_USER_UNKNOWN;
2397 goto out;
2400 _pam_log_debug(ctx, LOG_DEBUG,
2401 "username [%s] obtained", user);
2403 ccname = pam_getenv(pamh, "KRB5CCNAME");
2404 if (ccname == NULL) {
2405 _pam_log_debug(ctx, LOG_DEBUG,
2406 "user has no KRB5CCNAME environment");
2409 strncpy(request.data.logoff.user, user,
2410 sizeof(request.data.logoff.user) - 1);
2412 if (ccname) {
2413 strncpy(request.data.logoff.krb5ccname, ccname,
2414 sizeof(request.data.logoff.krb5ccname) - 1);
2417 pwd = getpwnam(user);
2418 if (pwd == NULL) {
2419 retval = PAM_USER_UNKNOWN;
2420 goto out;
2422 request.data.logoff.uid = pwd->pw_uid;
2424 request.flags = WBFLAG_PAM_KRB5 |
2425 WBFLAG_PAM_CONTACT_TRUSTDOM;
2427 retval = pam_winbind_request_log(ctx,
2428 WINBINDD_PAM_LOGOFF,
2429 &request, &response, user);
2432 out:
2434 _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, retval);
2436 _pam_winbind_free_context(ctx);
2438 return retval;
2442 * evaluate whether we need to re-authenticate with kerberos after a
2443 * password change
2445 * @param pamh PAM handle
2446 * @param ctrl PAM winbind options.
2447 * @param user The username
2449 * @return boolean Returns true if required, false if not.
2452 static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx,
2453 const char *user)
2456 /* Make sure that we only do this if a) the chauthtok got initiated
2457 * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any
2458 * later password change via the "passwd" command if done by the user
2459 * itself
2460 * NB. If we login from gdm or xdm and the password expires,
2461 * we change the password, but there is no memory cache.
2462 * Thus, even for passthrough login, we should do the
2463 * authentication again to update memory cache.
2464 * --- BoYang
2465 * */
2467 char *new_authtok_reqd_during_auth = NULL;
2468 struct passwd *pwd = NULL;
2470 _pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2471 &new_authtok_reqd_during_auth);
2472 pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH,
2473 NULL, NULL);
2475 if (new_authtok_reqd_during_auth) {
2476 return true;
2479 pwd = getpwnam(user);
2480 if (!pwd) {
2481 return false;
2484 if (getuid() == pwd->pw_uid) {
2485 return true;
2488 return false;
2492 PAM_EXTERN
2493 int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
2494 int argc, const char **argv)
2496 unsigned int lctrl;
2497 int ret;
2498 bool cached_login = false;
2500 /* <DO NOT free() THESE> */
2501 const char *user;
2502 char *pass_old, *pass_new;
2503 /* </DO NOT free() THESE> */
2505 char *Announce;
2507 int retry = 0;
2508 char *username_ret = NULL;
2509 struct winbindd_response response;
2510 struct pwb_context *ctx = NULL;
2512 ZERO_STRUCT(response);
2514 ret = _pam_winbind_init_context(pamh, flags, argc, argv, &ctx);
2515 if (ret) {
2516 goto out;
2519 _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx);
2521 cached_login = (ctx->ctrl & WINBIND_CACHED_LOGIN);
2523 /* clearing offline bit for auth */
2524 ctx->ctrl &= ~WINBIND_CACHED_LOGIN;
2527 * First get the name of a user
2529 ret = pam_get_user(pamh, &user, "Username: ");
2530 if (ret) {
2531 _pam_log(ctx, LOG_ERR,
2532 "password - could not identify user");
2533 goto out;
2536 if (user == NULL) {
2537 _pam_log(ctx, LOG_ERR, "username was NULL!");
2538 ret = PAM_USER_UNKNOWN;
2539 goto out;
2542 _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user);
2544 /* check if this is really a user in winbindd, not only in NSS */
2545 ret = valid_user(ctx, user);
2546 switch (ret) {
2547 case 1:
2548 ret = PAM_USER_UNKNOWN;
2549 goto out;
2550 case -1:
2551 ret = PAM_SYSTEM_ERR;
2552 goto out;
2553 default:
2554 break;
2558 * obtain and verify the current password (OLDAUTHTOK) for
2559 * the user.
2562 if (flags & PAM_PRELIM_CHECK) {
2563 time_t pwdlastset_prelim = 0;
2565 /* instruct user what is happening */
2566 #define greeting "Changing password for "
2567 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
2568 if (Announce == NULL) {
2569 _pam_log(ctx, LOG_CRIT,
2570 "password - out of memory");
2571 ret = PAM_BUF_ERR;
2572 goto out;
2574 (void) strcpy(Announce, greeting);
2575 (void) strcpy(Announce + sizeof(greeting) - 1, user);
2576 #undef greeting
2578 lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD;
2579 ret = _winbind_read_password(ctx, lctrl,
2580 Announce,
2581 "(current) NT password: ",
2582 NULL,
2583 (const char **) &pass_old);
2584 if (ret != PAM_SUCCESS) {
2585 _pam_log(ctx, LOG_NOTICE,
2586 "password - (old) token not obtained");
2587 goto out;
2590 /* verify that this is the password for this user */
2592 ret = winbind_auth_request(ctx, user, pass_old,
2593 NULL, NULL, 0, &response,
2594 &pwdlastset_prelim, NULL);
2596 if (ret != PAM_ACCT_EXPIRED &&
2597 ret != PAM_AUTHTOK_EXPIRED &&
2598 ret != PAM_NEW_AUTHTOK_REQD &&
2599 ret != PAM_SUCCESS) {
2600 pass_old = NULL;
2601 goto out;
2604 pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2605 (void *)pwdlastset_prelim, NULL);
2607 ret = pam_set_item(pamh, PAM_OLDAUTHTOK,
2608 (const void *) pass_old);
2609 pass_old = NULL;
2610 if (ret != PAM_SUCCESS) {
2611 _pam_log(ctx, LOG_CRIT,
2612 "failed to set PAM_OLDAUTHTOK");
2614 } else if (flags & PAM_UPDATE_AUTHTOK) {
2616 time_t pwdlastset_update = 0;
2619 * obtain the proposed password
2623 * get the old token back.
2626 ret = _pam_get_item(pamh, PAM_OLDAUTHTOK, &pass_old);
2628 if (ret != PAM_SUCCESS) {
2629 _pam_log(ctx, LOG_NOTICE,
2630 "user not authenticated");
2631 goto out;
2634 lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG;
2636 if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) {
2637 lctrl |= WINBIND_USE_FIRST_PASS_ARG;
2639 retry = 0;
2640 ret = PAM_AUTHTOK_ERR;
2641 while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
2643 * use_authtok is to force the use of a previously entered
2644 * password -- needed for pluggable password strength checking
2647 ret = _winbind_read_password(ctx, lctrl,
2648 NULL,
2649 "Enter new NT password: ",
2650 "Retype new NT password: ",
2651 (const char **)&pass_new);
2653 if (ret != PAM_SUCCESS) {
2654 _pam_log_debug(ctx, LOG_ALERT,
2655 "password - "
2656 "new password not obtained");
2657 pass_old = NULL;/* tidy up */
2658 goto out;
2662 * At this point we know who the user is and what they
2663 * propose as their new password. Verify that the new
2664 * password is acceptable.
2667 if (pass_new[0] == '\0') {/* "\0" password = NULL */
2668 pass_new = NULL;
2673 * By reaching here we have approved the passwords and must now
2674 * rebuild the password database file.
2676 _pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET,
2677 &pwdlastset_update);
2680 * if cached creds were enabled, make sure to set the
2681 * WINBIND_CACHED_LOGIN bit here in order to have winbindd
2682 * update the cached creds storage - gd
2684 if (cached_login) {
2685 ctx->ctrl |= WINBIND_CACHED_LOGIN;
2688 ret = winbind_chauthtok_request(ctx, user, pass_old,
2689 pass_new, pwdlastset_update);
2690 if (ret) {
2691 _pam_overwrite(pass_new);
2692 _pam_overwrite(pass_old);
2693 pass_old = pass_new = NULL;
2694 goto out;
2697 if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) {
2699 const char *member = NULL;
2700 const char *cctype = NULL;
2701 int warn_pwd_expire;
2703 member = get_member_from_config(ctx);
2704 cctype = get_krb5_cc_type_from_config(ctx);
2705 warn_pwd_expire = get_warn_pwd_expire_from_config(ctx);
2707 /* Keep WINBIND_CACHED_LOGIN bit for
2708 * authentication after changing the password.
2709 * This will update the cached credentials in case
2710 * that winbindd_dual_pam_chauthtok() fails
2711 * to update them.
2712 * --- BoYang
2713 * */
2715 ret = winbind_auth_request(ctx, user, pass_new,
2716 member, cctype, 0, &response,
2717 NULL, &username_ret);
2718 _pam_overwrite(pass_new);
2719 _pam_overwrite(pass_old);
2720 pass_old = pass_new = NULL;
2722 if (ret == PAM_SUCCESS) {
2724 /* warn a user if the password is about to
2725 * expire soon */
2726 _pam_warn_password_expiry(ctx, &response,
2727 warn_pwd_expire,
2728 NULL);
2730 /* set some info3 info for other modules in the
2731 * stack */
2732 _pam_set_data_info3(ctx, &response);
2734 /* put krb5ccname into env */
2735 _pam_setup_krb5_env(ctx,
2736 response.data.auth.krb5ccname);
2738 if (username_ret) {
2739 pam_set_item(pamh, PAM_USER,
2740 username_ret);
2741 _pam_log_debug(ctx, LOG_INFO,
2742 "Returned user was '%s'",
2743 username_ret);
2744 free(username_ret);
2748 goto out;
2750 } else {
2751 ret = PAM_SERVICE_ERR;
2754 out:
2756 /* Deal with offline errors. */
2757 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2758 "NT_STATUS_NO_LOGON_SERVERS");
2759 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2760 "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND");
2761 PAM_WB_REMARK_CHECK_RESPONSE(ctx, response,
2762 "NT_STATUS_ACCESS_DENIED");
2764 _PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);
2766 _pam_winbind_free_context(ctx);
2768 return ret;
2771 #ifdef PAM_STATIC
2773 /* static module data */
2775 struct pam_module _pam_winbind_modstruct = {
2776 MODULE_NAME,
2777 pam_sm_authenticate,
2778 pam_sm_setcred,
2779 pam_sm_acct_mgmt,
2780 pam_sm_open_session,
2781 pam_sm_close_session,
2782 pam_sm_chauthtok
2785 #endif
2788 * Copyright (c) Andrew Tridgell <tridge@samba.org> 2000
2789 * Copyright (c) Tim Potter <tpot@samba.org> 2000
2790 * Copyright (c) Andrew Bartlettt <abartlet@samba.org> 2002
2791 * Copyright (c) Guenther Deschner <gd@samba.org> 2005-2008
2792 * Copyright (c) Jan Rêkorajski 1999.
2793 * Copyright (c) Andrew G. Morgan 1996-8.
2794 * Copyright (c) Alex O. Yuriev, 1996.
2795 * Copyright (c) Cristian Gafton 1996.
2796 * Copyright (C) Elliot Lee <sopwith@redhat.com> 1996, Red Hat Software.
2798 * Redistribution and use in source and binary forms, with or without
2799 * modification, are permitted provided that the following conditions
2800 * are met:
2801 * 1. Redistributions of source code must retain the above copyright
2802 * notice, and the entire permission notice in its entirety,
2803 * including the disclaimer of warranties.
2804 * 2. Redistributions in binary form must reproduce the above copyright
2805 * notice, this list of conditions and the following disclaimer in the
2806 * documentation and/or other materials provided with the distribution.
2807 * 3. The name of the author may not be used to endorse or promote
2808 * products derived from this software without specific prior
2809 * written permission.
2811 * ALTERNATIVELY, this product may be distributed under the terms of
2812 * the GNU Public License, in which case the provisions of the GPL are
2813 * required INSTEAD OF the above restrictions. (This clause is
2814 * necessary due to a potential bad interaction between the GPL and
2815 * the restrictions contained in a BSD-style copyright.)
2817 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
2818 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2819 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2820 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2821 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2822 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2823 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2824 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2825 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2826 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2827 * OF THE POSSIBILITY OF SUCH DAMAGE.