/* pam_winbind module Copyright Andrew Tridgell 2000 Copyright Tim Potter 2000 Copyright Andrew Bartlett 2002 Copyright Guenther Deschner 2005-2008 largely based on pam_userdb by Cristian Gafton also contains large slabs of code from pam_unix by Elliot Lee (see copyright below for full details) */ #include "pam_winbind.h" enum pam_winbind_request_type { PAM_WINBIND_AUTHENTICATE, PAM_WINBIND_SETCRED, PAM_WINBIND_ACCT_MGMT, PAM_WINBIND_OPEN_SESSION, PAM_WINBIND_CLOSE_SESSION, PAM_WINBIND_CHAUTHTOK, PAM_WINBIND_CLEANUP }; static int wbc_error_to_pam_error(wbcErr status) { switch (status) { case WBC_ERR_SUCCESS: return PAM_SUCCESS; case WBC_ERR_NOT_IMPLEMENTED: return PAM_SERVICE_ERR; case WBC_ERR_UNKNOWN_FAILURE: break; case WBC_ERR_NO_MEMORY: return PAM_BUF_ERR; case WBC_ERR_INVALID_SID: case WBC_ERR_INVALID_PARAM: break; case WBC_ERR_WINBIND_NOT_AVAILABLE: return PAM_AUTHINFO_UNAVAIL; case WBC_ERR_DOMAIN_NOT_FOUND: return PAM_AUTHINFO_UNAVAIL; case WBC_ERR_INVALID_RESPONSE: return PAM_BUF_ERR; case WBC_ERR_NSS_ERROR: return PAM_USER_UNKNOWN; case WBC_ERR_AUTH_ERROR: return PAM_AUTH_ERR; case WBC_ERR_UNKNOWN_USER: return PAM_USER_UNKNOWN; case WBC_ERR_UNKNOWN_GROUP: return PAM_USER_UNKNOWN; case WBC_ERR_PWD_CHANGE_FAILED: break; } /* be paranoid */ return PAM_AUTH_ERR; } static const char *_pam_error_code_str(int err) { switch (err) { case PAM_SUCCESS: return "PAM_SUCCESS"; case PAM_OPEN_ERR: return "PAM_OPEN_ERR"; case PAM_SYMBOL_ERR: return "PAM_SYMBOL_ERR"; case PAM_SERVICE_ERR: return "PAM_SERVICE_ERR"; case PAM_SYSTEM_ERR: return "PAM_SYSTEM_ERR"; case PAM_BUF_ERR: return "PAM_BUF_ERR"; case PAM_PERM_DENIED: return "PAM_PERM_DENIED"; case PAM_AUTH_ERR: return "PAM_AUTH_ERR"; case PAM_CRED_INSUFFICIENT: return "PAM_CRED_INSUFFICIENT"; case PAM_AUTHINFO_UNAVAIL: return "PAM_AUTHINFO_UNAVAIL"; case PAM_USER_UNKNOWN: return "PAM_USER_UNKNOWN"; case PAM_MAXTRIES: return "PAM_MAXTRIES"; case PAM_NEW_AUTHTOK_REQD: return "PAM_NEW_AUTHTOK_REQD"; case PAM_ACCT_EXPIRED: return "PAM_ACCT_EXPIRED"; case PAM_SESSION_ERR: return "PAM_SESSION_ERR"; case PAM_CRED_UNAVAIL: return "PAM_CRED_UNAVAIL"; case PAM_CRED_EXPIRED: return "PAM_CRED_EXPIRED"; case PAM_CRED_ERR: return "PAM_CRED_ERR"; case PAM_NO_MODULE_DATA: return "PAM_NO_MODULE_DATA"; case PAM_CONV_ERR: return "PAM_CONV_ERR"; case PAM_AUTHTOK_ERR: return "PAM_AUTHTOK_ERR"; case PAM_AUTHTOK_RECOVER_ERR: return "PAM_AUTHTOK_RECOVER_ERR"; case PAM_AUTHTOK_LOCK_BUSY: return "PAM_AUTHTOK_LOCK_BUSY"; case PAM_AUTHTOK_DISABLE_AGING: return "PAM_AUTHTOK_DISABLE_AGING"; case PAM_TRY_AGAIN: return "PAM_TRY_AGAIN"; case PAM_IGNORE: return "PAM_IGNORE"; case PAM_ABORT: return "PAM_ABORT"; case PAM_AUTHTOK_EXPIRED: return "PAM_AUTHTOK_EXPIRED"; #ifdef PAM_MODULE_UNKNOWN case PAM_MODULE_UNKNOWN: return "PAM_MODULE_UNKNOWN"; #endif #ifdef PAM_BAD_ITEM case PAM_BAD_ITEM: return "PAM_BAD_ITEM"; #endif #ifdef PAM_CONV_AGAIN case PAM_CONV_AGAIN: return "PAM_CONV_AGAIN"; #endif #ifdef PAM_INCOMPLETE case PAM_INCOMPLETE: return "PAM_INCOMPLETE"; #endif default: return NULL; } } #define _PAM_LOG_FUNCTION_ENTER(function, ctx) \ do { \ _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] ENTER: " \ function " (flags: 0x%04x)", ctx->pamh, ctx->flags); \ _pam_log_state(ctx); \ } while (0) #define _PAM_LOG_FUNCTION_LEAVE(function, ctx, retval) \ do { \ _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] LEAVE: " \ function " returning %d (%s)", ctx ? ctx->pamh : NULL, retval, \ _pam_error_code_str(retval)); \ _pam_log_state(ctx); \ } while (0) /* data tokens */ #define MAX_PASSWD_TRIES 3 #ifdef HAVE_GETTEXT static char initialized = 0; static inline void textdomain_init(void); static inline void textdomain_init(void) { if (!initialized) { bindtextdomain(MODULE_NAME, LOCALEDIR); initialized = 1; } return; } #endif /* some syslogging */ static void _pam_log_int(const pam_handle_t *pamh, int err, const char *format, va_list args) PRINTF_ATTRIBUTE(3, 0); #ifdef HAVE_PAM_VSYSLOG static void _pam_log_int(const pam_handle_t *pamh, int err, const char *format, va_list args) { pam_vsyslog(pamh, err, format, args); } #else static void _pam_log_int(const pam_handle_t *pamh, int err, const char *format, va_list args) { char *base = NULL; va_list args2; const char *service; int ret; va_copy(args2, args); pam_get_item(pamh, PAM_SERVICE, (const void **) &service); ret = vasprintf(&base, format, args); if (ret == -1) { /* what else todo ? */ vsyslog(err, format, args2); va_end(args2); return; } syslog(err, "%s(%s): %s", MODULE_NAME, service, base); SAFE_FREE(base); va_end(args2); } #endif /* HAVE_PAM_VSYSLOG */ static bool _pam_log_is_silent(int ctrl) { return on(ctrl, WINBIND_SILENT); } static void _pam_log(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4); static void _pam_log(struct pwb_context *r, int err, const char *format, ...) { va_list args; if (_pam_log_is_silent(r->ctrl)) { return; } va_start(args, format); _pam_log_int(r->pamh, err, format, args); va_end(args); } static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5); static void __pam_log(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) { va_list args; if (_pam_log_is_silent(ctrl)) { return; } va_start(args, format); _pam_log_int(pamh, err, format, args); va_end(args); } static bool _pam_log_is_debug_enabled(int ctrl) { if (ctrl == -1) { return false; } if (_pam_log_is_silent(ctrl)) { return false; } if (!(ctrl & WINBIND_DEBUG_ARG)) { return false; } return true; } static bool _pam_log_is_debug_state_enabled(int ctrl) { if (!(ctrl & WINBIND_DEBUG_STATE)) { return false; } return _pam_log_is_debug_enabled(ctrl); } static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) PRINTF_ATTRIBUTE(3,4); static void _pam_log_debug(struct pwb_context *r, int err, const char *format, ...) { va_list args; if (!r || !_pam_log_is_debug_enabled(r->ctrl)) { return; } va_start(args, format); _pam_log_int(r->pamh, err, format, args); va_end(args); } static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) PRINTF_ATTRIBUTE(4,5); static void __pam_log_debug(const pam_handle_t *pamh, int ctrl, int err, const char *format, ...) { va_list args; if (!_pam_log_is_debug_enabled(ctrl)) { return; } va_start(args, format); _pam_log_int(pamh, err, format, args); va_end(args); } static void _pam_log_state_datum(struct pwb_context *ctx, int item_type, const char *key, int is_string) { const void *data = NULL; if (item_type != 0) { pam_get_item(ctx->pamh, item_type, &data); } else { pam_get_data(ctx->pamh, key, &data); } if (data != NULL) { const char *type = (item_type != 0) ? "ITEM" : "DATA"; if (is_string != 0) { _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] STATE: %s(%s) = \"%s\" (%p)", ctx->pamh, type, key, (const char *)data, data); } else { _pam_log_debug(ctx, LOG_DEBUG, "[pamh: %p] STATE: %s(%s) = %p", ctx->pamh, type, key, data); } } } #define _PAM_LOG_STATE_DATA_POINTER(ctx, module_data_name) \ _pam_log_state_datum(ctx, 0, module_data_name, 0) #define _PAM_LOG_STATE_DATA_STRING(ctx, module_data_name) \ _pam_log_state_datum(ctx, 0, module_data_name, 1) #define _PAM_LOG_STATE_ITEM_POINTER(ctx, item_type) \ _pam_log_state_datum(ctx, item_type, #item_type, 0) #define _PAM_LOG_STATE_ITEM_STRING(ctx, item_type) \ _pam_log_state_datum(ctx, item_type, #item_type, 1) #ifdef DEBUG_PASSWORD #define _LOG_PASSWORD_AS_STRING 1 #else #define _LOG_PASSWORD_AS_STRING 0 #endif #define _PAM_LOG_STATE_ITEM_PASSWORD(ctx, item_type) \ _pam_log_state_datum(ctx, item_type, #item_type, \ _LOG_PASSWORD_AS_STRING) /* * wrapper to preserve old behaviour of iniparser which ignored * key values that had no value assigned like * key = * for a key like above newer iniparser will return a zero-length * string, previously iniparser would return NULL * * JRA: For compatibility, tiniparser behaves like iniparser. */ static const char *tiniparser_getstring_nonempty(struct tiniparser_dictionary *d, const char *key, const char *def) { const char *ret = tiniparser_getstring(d, key, def); if (ret && strlen(ret) == 0) { ret = NULL; } return ret; } static void _pam_log_state(struct pwb_context *ctx) { if (!ctx || !_pam_log_is_debug_state_enabled(ctx->ctrl)) { return; } _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_SERVICE); _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER); _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_TTY); _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RHOST); _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_RUSER); _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_OLDAUTHTOK); _PAM_LOG_STATE_ITEM_PASSWORD(ctx, PAM_AUTHTOK); _PAM_LOG_STATE_ITEM_STRING(ctx, PAM_USER_PROMPT); _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_CONV); #ifdef PAM_FAIL_DELAY _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_FAIL_DELAY); #endif #ifdef PAM_REPOSITORY _PAM_LOG_STATE_ITEM_POINTER(ctx, PAM_REPOSITORY); #endif _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_HOMEDIR); _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSCRIPT); _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_LOGONSERVER); _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_PROFILEPATH); _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_NEW_AUTHTOK_REQD); /* Use atoi to get PAM result code */ _PAM_LOG_STATE_DATA_STRING(ctx, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH); _PAM_LOG_STATE_DATA_POINTER(ctx, PAM_WINBIND_PWD_LAST_SET); } static int _pam_parse(const pam_handle_t *pamh, int flags, int argc, const char **argv, enum pam_winbind_request_type type, struct tiniparser_dictionary **result_d) { int ctrl = 0; const char *config_file = NULL; int i; const char **v; struct tiniparser_dictionary *d = NULL; if (flags & PAM_SILENT) { ctrl |= WINBIND_SILENT; } for (i=argc,v=argv; i-- > 0; ++v) { if (!strncasecmp(*v, "config", strlen("config"))) { ctrl |= WINBIND_CONFIG_FILE; config_file = v[i]; break; } } if (config_file == NULL) { config_file = PAM_WINBIND_CONFIG_FILE; } d = tiniparser_load(config_file); if (d == NULL) { goto config_from_pam; } if (tiniparser_getboolean(d, "global:debug", false)) { ctrl |= WINBIND_DEBUG_ARG; } if (tiniparser_getboolean(d, "global:debug_state", false)) { ctrl |= WINBIND_DEBUG_STATE; } if (tiniparser_getboolean(d, "global:cached_login", false)) { ctrl |= WINBIND_CACHED_LOGIN; } if (tiniparser_getboolean(d, "global:krb5_auth", false)) { ctrl |= WINBIND_KRB5_AUTH; } if (tiniparser_getboolean(d, "global:silent", false)) { ctrl |= WINBIND_SILENT; } if (tiniparser_getstring_nonempty(d, "global:krb5_ccache_type", NULL) != NULL) { ctrl |= WINBIND_KRB5_CCACHE_TYPE; } if ((tiniparser_getstring_nonempty(d, "global:require-membership-of", NULL) != NULL) || (tiniparser_getstring_nonempty(d, "global:require_membership_of", NULL) != NULL)) { ctrl |= WINBIND_REQUIRED_MEMBERSHIP; } if (tiniparser_getboolean(d, "global:try_first_pass", false)) { ctrl |= WINBIND_TRY_FIRST_PASS_ARG; } if (tiniparser_getint(d, "global:warn_pwd_expire", 0)) { ctrl |= WINBIND_WARN_PWD_EXPIRE; } if (tiniparser_getboolean(d, "global:mkhomedir", false)) { ctrl |= WINBIND_MKHOMEDIR; } config_from_pam: /* step through arguments */ for (i=argc,v=argv; i-- > 0; ++v) { /* generic options */ if (!strcmp(*v,"debug")) ctrl |= WINBIND_DEBUG_ARG; else if (!strcasecmp(*v, "debug_state")) ctrl |= WINBIND_DEBUG_STATE; else if (!strcasecmp(*v, "silent")) ctrl |= WINBIND_SILENT; else if (!strcasecmp(*v, "use_authtok")) ctrl |= WINBIND_USE_AUTHTOK_ARG; else if (!strcasecmp(*v, "try_authtok")) ctrl |= WINBIND_TRY_AUTHTOK_ARG; else if (!strcasecmp(*v, "use_first_pass")) ctrl |= WINBIND_USE_FIRST_PASS_ARG; else if (!strcasecmp(*v, "try_first_pass")) ctrl |= WINBIND_TRY_FIRST_PASS_ARG; else if (!strcasecmp(*v, "unknown_ok")) ctrl |= WINBIND_UNKNOWN_OK_ARG; else if ((type == PAM_WINBIND_AUTHENTICATE || type == PAM_WINBIND_SETCRED) && !strncasecmp(*v, "require_membership_of", strlen("require_membership_of"))) ctrl |= WINBIND_REQUIRED_MEMBERSHIP; else if ((type == PAM_WINBIND_AUTHENTICATE || type == PAM_WINBIND_SETCRED) && !strncasecmp(*v, "require-membership-of", strlen("require-membership-of"))) ctrl |= WINBIND_REQUIRED_MEMBERSHIP; else if (!strcasecmp(*v, "krb5_auth")) ctrl |= WINBIND_KRB5_AUTH; else if (!strncasecmp(*v, "krb5_ccache_type", strlen("krb5_ccache_type"))) ctrl |= WINBIND_KRB5_CCACHE_TYPE; else if (!strcasecmp(*v, "cached_login")) ctrl |= WINBIND_CACHED_LOGIN; else if (!strcasecmp(*v, "mkhomedir")) ctrl |= WINBIND_MKHOMEDIR; else if (!strncasecmp(*v, "warn_pwd_expire", strlen("warn_pwd_expire"))) ctrl |= WINBIND_WARN_PWD_EXPIRE; else if (type != PAM_WINBIND_CLEANUP) { __pam_log(pamh, ctrl, LOG_ERR, "pam_parse: unknown option: %s", *v); return -1; } } if (result_d) { *result_d = d; } else { if (d) { tiniparser_freedict(d); } } return ctrl; }; static int _pam_winbind_free_context(struct pwb_context *ctx) { if (!ctx) { return 0; } if (ctx->dict) { tiniparser_freedict(ctx->dict); } wbcCtxFree(ctx->wbc_ctx); return 0; } static int _pam_winbind_init_context(pam_handle_t *pamh, int flags, int argc, const char **argv, enum pam_winbind_request_type type, struct pwb_context **ctx_p) { struct pwb_context *r = NULL; const char *service = NULL; char service_name[32] = {0}; int ctrl_code; #ifdef HAVE_GETTEXT textdomain_init(); #endif r = talloc_zero(NULL, struct pwb_context); if (!r) { return PAM_BUF_ERR; } talloc_set_destructor(r, _pam_winbind_free_context); r->pamh = pamh; r->flags = flags; r->argc = argc; r->argv = argv; ctrl_code = _pam_parse(pamh, flags, argc, argv, type, &r->dict); if (ctrl_code == -1) { TALLOC_FREE(r); return PAM_SYSTEM_ERR; } r->ctrl = ctrl_code; r->wbc_ctx = wbcCtxCreate(); if (r->wbc_ctx == NULL) { TALLOC_FREE(r); return PAM_SYSTEM_ERR; } pam_get_item(pamh, PAM_SERVICE, (const void **)&service); snprintf(service_name, sizeof(service_name), "PAM_WINBIND[%s]", service); wbcSetClientProcessName(service_name); *ctx_p = r; return PAM_SUCCESS; } static void _pam_winbind_cleanup_func(pam_handle_t *pamh, void *data, int error_status) { int ctrl = _pam_parse(pamh, 0, 0, NULL, PAM_WINBIND_CLEANUP, NULL); if (_pam_log_is_debug_state_enabled(ctrl)) { __pam_log_debug(pamh, ctrl, LOG_DEBUG, "[pamh: %p] CLEAN: cleaning up PAM data %p " "(error_status = %d)", pamh, data, error_status); } TALLOC_FREE(data); } static const struct ntstatus_errors { const char *ntstatus_string; const char *error_string; } ntstatus_errors[] = { {"NT_STATUS_OK", N_("Success")}, {"NT_STATUS_BACKUP_CONTROLLER", N_("No primary Domain Controller available")}, {"NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", N_("No domain controllers found")}, {"NT_STATUS_NO_LOGON_SERVERS", N_("No logon servers")}, {"NT_STATUS_PWD_TOO_SHORT", N_("Password too short")}, {"NT_STATUS_PWD_TOO_RECENT", N_("The password of this user is too recent to change")}, {"NT_STATUS_PWD_HISTORY_CONFLICT", N_("Password is already in password history")}, {"NT_STATUS_PASSWORD_EXPIRED", N_("Your password has expired")}, {"NT_STATUS_PASSWORD_MUST_CHANGE", N_("You need to change your password now")}, {"NT_STATUS_INVALID_WORKSTATION", N_("You are not allowed to logon from this workstation")}, {"NT_STATUS_INVALID_LOGON_HOURS", N_("You are not allowed to logon at this time")}, {"NT_STATUS_ACCOUNT_EXPIRED", N_("Your account has expired. " "Please contact your System administrator")}, /* SCNR */ {"NT_STATUS_ACCOUNT_DISABLED", N_("Your account is disabled. " "Please contact your System administrator")}, /* SCNR */ {"NT_STATUS_ACCOUNT_LOCKED_OUT", N_("Your account has been locked. " "Please contact your System administrator")}, /* SCNR */ {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT", N_("Invalid Trust Account")}, {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT", N_("Invalid Trust Account")}, {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT", N_("Invalid Trust Account")}, {"NT_STATUS_ACCESS_DENIED", N_("Access is denied")}, {NULL, NULL} }; static const char *_get_ntstatus_error_string(const char *nt_status_string) { int i; for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) { if (!strcasecmp(ntstatus_errors[i].ntstatus_string, nt_status_string)) { return _(ntstatus_errors[i].error_string); } } return NULL; } /* --- authentication management functions --- */ /* Attempt a conversation */ static int converse(const pam_handle_t *pamh, int nargs, const struct pam_message **message, struct pam_response **response) { int retval; const struct pam_conv *conv; retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv); if (retval == PAM_SUCCESS) { retval = conv->conv(nargs, discard_const_p(const struct pam_message *, message), response, conv->appdata_ptr); } return retval; /* propagate error status */ } static int _make_remark(struct pwb_context *ctx, int type, const char *text) { int retval = PAM_SUCCESS; const struct pam_message *pmsg[1]; struct pam_message msg[1]; struct pam_response *resp; if (ctx->flags & WINBIND_SILENT) { return PAM_SUCCESS; } pmsg[0] = &msg[0]; msg[0].msg = discard_const_p(char, text); msg[0].msg_style = type; resp = NULL; retval = converse(ctx->pamh, 1, pmsg, &resp); if (resp) { _pam_drop_reply(resp, 1); } return retval; } static int _make_remark_v(struct pwb_context *ctx, int type, const char *format, va_list args) PRINTF_ATTRIBUTE(3, 0); static int _make_remark_v(struct pwb_context *ctx, int type, const char *format, va_list args) { char *var; int ret; ret = vasprintf(&var, format, args); if (ret < 0) { _pam_log(ctx, LOG_ERR, "memory allocation failure"); return ret; } ret = _make_remark(ctx, type, var); SAFE_FREE(var); return ret; } static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) PRINTF_ATTRIBUTE(3,4); static int _make_remark_format(struct pwb_context *ctx, int type, const char *format, ...) { int ret; va_list args; va_start(args, format); ret = _make_remark_v(ctx, type, format, args); va_end(args); return ret; } static int pam_winbind_request_log(struct pwb_context *ctx, int retval, const char *user, const char *fn) { switch (retval) { case PAM_AUTH_ERR: /* incorrect password */ _pam_log(ctx, LOG_WARNING, "user '%s' denied access " "(incorrect password or invalid membership)", user); return retval; case PAM_ACCT_EXPIRED: /* account expired */ _pam_log(ctx, LOG_WARNING, "user '%s' account expired", user); return retval; case PAM_AUTHTOK_EXPIRED: /* password expired */ _pam_log(ctx, LOG_WARNING, "user '%s' password expired", user); return retval; case PAM_NEW_AUTHTOK_REQD: /* new password required */ _pam_log(ctx, LOG_WARNING, "user '%s' new password " "required", user); return retval; case PAM_USER_UNKNOWN: /* the user does not exist */ _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found", user); if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) { return PAM_IGNORE; } return retval; case PAM_AUTHTOK_ERR: /* Authentication token manipulation error */ _pam_log(ctx, LOG_WARNING, "user `%s' authentication token change failed " "(pwd complexity/history/min_age not met?)", user); return retval; case PAM_SUCCESS: /* Otherwise, the authentication looked good */ if (strcmp(fn, "wbcLogonUser") == 0) { _pam_log(ctx, LOG_NOTICE, "user '%s' granted access", user); } else { _pam_log(ctx, LOG_NOTICE, "user '%s' OK", user); } return retval; default: /* we don't know anything about this return value */ _pam_log(ctx, LOG_ERR, "internal module error (retval = %s(%d), user = '%s')", _pam_error_code_str(retval), retval, user); return retval; } } static int wbc_auth_error_to_pam_error(struct pwb_context *ctx, struct wbcAuthErrorInfo *e, wbcErr status, const char *username, const char *fn) { int ret = PAM_AUTH_ERR; if (WBC_ERROR_IS_OK(status)) { _pam_log_debug(ctx, LOG_DEBUG, "request %s succeeded", fn); ret = PAM_SUCCESS; return pam_winbind_request_log(ctx, ret, username, fn); } if (e) { if (e->pam_error != PAM_SUCCESS) { _pam_log(ctx, LOG_ERR, "request %s failed: %s, " "PAM error: %s (%d), NTSTATUS: %s, " "Error message was: %s", fn, wbcErrorString(status), _pam_error_code_str(e->pam_error), e->pam_error, e->nt_string, e->display_string); ret = e->pam_error; return pam_winbind_request_log(ctx, ret, username, fn); } _pam_log(ctx, LOG_ERR, "request %s failed, but PAM error 0!", fn); ret = PAM_SERVICE_ERR; return pam_winbind_request_log(ctx, ret, username, fn); } ret = wbc_error_to_pam_error(status); return pam_winbind_request_log(ctx, ret, username, fn); } #if defined(HAVE_PAM_RADIO_TYPE) static bool _pam_winbind_change_pwd(struct pwb_context *ctx) { struct pam_message msg; const struct pam_message *pmsg; struct pam_response *resp = NULL; int ret; bool retval = false; pmsg = &msg; msg.msg_style = PAM_RADIO_TYPE; msg.msg = _("Do you want to change your password now?"); ret = converse(ctx->pamh, 1, &pmsg, &resp); if (resp == NULL) { if (ret == PAM_SUCCESS) { _pam_log(ctx, LOG_CRIT, "pam_winbind: system error!\n"); return false; } } if (ret != PAM_SUCCESS) { return false; } _pam_log(ctx, LOG_CRIT, "Received [%s] reply from application.\n", resp->resp); if ((resp->resp != NULL) && (strcasecmp(resp->resp, "yes") == 0)) { retval = true; } _pam_drop_reply(resp, 1); return retval; } #else static bool _pam_winbind_change_pwd(struct pwb_context *ctx) { return false; } #endif /** * send a password expiry message if required * * @param ctx PAM winbind context. * @param next_change expected (calculated) next expiry date. * @param already_expired pointer to a boolean to indicate if the password is * already expired. * * @return boolean Returns true if message has been sent, false if not. */ static bool _pam_send_password_expiry_message(struct pwb_context *ctx, time_t next_change, time_t now, int warn_pwd_expire, bool *already_expired, bool *change_pwd) { int days = 0; struct tm tm_now, tm_next_change; bool retval = false; int ret; if (already_expired) { *already_expired = false; } if (change_pwd) { *change_pwd = false; } if (next_change <= now) { PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PASSWORD_EXPIRED"); if (already_expired) { *already_expired = true; } return true; } if ((next_change < 0) || (next_change > now + warn_pwd_expire * SECONDS_PER_DAY)) { return false; } if ((localtime_r(&now, &tm_now) == NULL) || (localtime_r(&next_change, &tm_next_change) == NULL)) { return false; } days = (tm_next_change.tm_yday+tm_next_change.tm_year*365) - (tm_now.tm_yday+tm_now.tm_year*365); if (days == 0) { ret = _make_remark(ctx, PAM_TEXT_INFO, _("Your password expires today.\n")); /* * If change_pwd and already_expired is null. * We are just sending a notification message. * We don't expect any response in this case. */ if (!change_pwd && !already_expired) { return true; } /* * successfully sent the warning message. * Give the user a chance to change pwd. */ if (ret == PAM_SUCCESS) { if (change_pwd) { retval = _pam_winbind_change_pwd(ctx); if (retval) { *change_pwd = true; } } } return true; } if (days > 0 && days < warn_pwd_expire) { ret = _make_remark_format(ctx, PAM_TEXT_INFO, _("Your password will expire in %d %s.\n"), days, (days > 1) ? _("days"):_("day")); /* * If change_pwd and already_expired is null. * We are just sending a notification message. * We don't expect any response in this case. */ if (!change_pwd && !already_expired) { return true; } /* * successfully sent the warning message. * Give the user a chance to change pwd. */ if (ret == PAM_SUCCESS) { if (change_pwd) { retval = _pam_winbind_change_pwd(ctx); if (retval) { *change_pwd = true; } } } return true; } return false; } /** * Send a warning if the password expires in the near future * * @param ctx PAM winbind context. * @param response The full authentication response structure. * @param already_expired boolean, is the pwd already expired? * * @return void. */ static void _pam_warn_password_expiry(struct pwb_context *ctx, const struct wbcAuthUserInfo *info, int warn_pwd_expire, bool *already_expired, bool *change_pwd) { time_t now = time(NULL); time_t next_change = 0; if (info == NULL) { return; } if (already_expired) { *already_expired = false; } if (change_pwd) { *change_pwd = false; } /* accounts with WBC_ACB_PWNOEXP set never receive a warning */ if (info->acct_flags & WBC_ACB_PWNOEXP) { return; } /* no point in sending a warning if this is a grace logon */ if (PAM_WB_GRACE_LOGON(info->user_flags)) { return; } /* check if the info3 must change timestamp has been set */ next_change = info->pass_must_change_time; if (_pam_send_password_expiry_message(ctx, next_change, now, warn_pwd_expire, already_expired, change_pwd)) { return; } /* no warning sent */ } #define IS_SID_STRING(name) (strncmp("S-", name, 2) == 0) /** * Append a string, making sure not to overflow and to always return a * NULL-terminated string. * * @param dest Destination string buffer (must already be NULL-terminated). * @param src Source string buffer. * @param dest_buffer_size Size of dest buffer in bytes. * * @return false if dest buffer is not big enough (no bytes copied), true on * success. */ static bool safe_append_string(char *dest, const char *src, int dest_buffer_size) { size_t len; len = strlcat(dest, src, dest_buffer_size); return (len < dest_buffer_size); } /** * Convert a names into a SID string, appending it to a buffer. * * @param ctx PAM winbind context. * @param user User in PAM request. * @param name Name to convert. * @param sid_list_buffer Where to append the string sid. * @param sid_list_buffer Size of sid_list_buffer (in bytes). * * @return false on failure, true on success. */ static bool winbind_name_to_sid_string(struct pwb_context *ctx, const char *user, const char *name, char *sid_list_buffer, int sid_list_buffer_size) { char sid_string[WBC_SID_STRING_BUFLEN]; /* lookup name? */ if (IS_SID_STRING(name)) { strlcpy(sid_string, name, sizeof(sid_string)); } else { wbcErr wbc_status; struct wbcDomainSid sid; enum wbcSidType type; _pam_log_debug(ctx, LOG_DEBUG, "no sid given, looking up: %s\n", name); wbc_status = wbcCtxLookupName(ctx->wbc_ctx, "", name, &sid, &type); if (!WBC_ERROR_IS_OK(wbc_status)) { _pam_log(ctx, LOG_INFO, "could not lookup name: %s\n", name); return false; } wbcSidToStringBuf(&sid, sid_string, sizeof(sid_string)); } if (!safe_append_string(sid_list_buffer, sid_string, sid_list_buffer_size)) { return false; } return true; } /** * Convert a list of names into a list of sids. * * @param ctx PAM winbind context. * @param user User in PAM request. * @param name_list List of names or string sids, separated by commas. * @param sid_list_buffer Where to put the list of string sids. * @param sid_list_buffer Size of sid_list_buffer (in bytes). * * @return false on failure, true on success. */ static bool winbind_name_list_to_sid_string_list(struct pwb_context *ctx, const char *user, const char *name_list, char *sid_list_buffer, int sid_list_buffer_size) { bool result = false; char *current_name = NULL; const char *search_location; const char *comma; int len; if (sid_list_buffer_size > 0) { sid_list_buffer[0] = 0; } search_location = name_list; while ((comma = strchr(search_location, ',')) != NULL) { current_name = strndup(search_location, comma - search_location); if (NULL == current_name) { goto out; } if (!winbind_name_to_sid_string(ctx, user, current_name, sid_list_buffer, sid_list_buffer_size)) { /* * If one group name failed, we must not fail * the authentication totally, continue with * the following group names. If user belongs to * one of the valid groups, we must allow it * login. -- BoYang */ _pam_log(ctx, LOG_INFO, "cannot convert group %s to sid, " "check if group %s is valid group.", current_name, current_name); _make_remark_format(ctx, PAM_TEXT_INFO, _("Cannot convert group %s " "to sid, please contact your administrator to see " "if group %s is valid."), current_name, current_name); SAFE_FREE(current_name); search_location = comma + 1; continue; } SAFE_FREE(current_name); if (!safe_append_string(sid_list_buffer, ",", sid_list_buffer_size)) { goto out; } search_location = comma + 1; } if (!winbind_name_to_sid_string(ctx, user, search_location, sid_list_buffer, sid_list_buffer_size)) { _pam_log(ctx, LOG_INFO, "cannot convert group %s to sid, " "check if group %s is valid group.", search_location, search_location); _make_remark_format(ctx, PAM_TEXT_INFO, _("Cannot convert group %s " "to sid, please contact your administrator to see " "if group %s is valid."), search_location, search_location); /* If no valid groups were converted we should fail outright */ if (name_list != NULL && strlen(sid_list_buffer) == 0) { result = false; goto out; } /* * The lookup of the last name failed.. * It results in require_member_of_sid ends with ',' * It is malformatted parameter here, overwrite the last ','. */ len = strlen(sid_list_buffer); if ((len != 0) && (sid_list_buffer[len - 1] == ',')) { sid_list_buffer[len - 1] = '\0'; } } result = true; out: SAFE_FREE(current_name); return result; } /** * put krb5ccname variable into environment * * @param ctx PAM winbind context. * @param krb5ccname env variable retrieved from winbindd. * * @return void. */ static void _pam_setup_krb5_env(struct pwb_context *ctx, struct wbcLogonUserInfo *info) { char *var = NULL; int ret; uint32_t i; const char *krb5ccname = NULL; if (off(ctx->ctrl, WINBIND_KRB5_AUTH)) { return; } if (!info) { return; } for (i=0; i < info->num_blobs; i++) { if (strcasecmp(info->blobs[i].name, "krb5ccname") == 0) { krb5ccname = (const char *)info->blobs[i].blob.data; break; } } if (!krb5ccname || (strlen(krb5ccname) == 0)) { return; } _pam_log_debug(ctx, LOG_DEBUG, "request returned KRB5CCNAME: %s", krb5ccname); if (asprintf(&var, "KRB5CCNAME=%s", krb5ccname) == -1) { return; } ret = pam_putenv(ctx->pamh, var); if (ret != PAM_SUCCESS) { _pam_log(ctx, LOG_ERR, "failed to set KRB5CCNAME to %s: %s", var, pam_strerror(ctx->pamh, ret)); } free(var); } /** * Copy unix username if available (further processed in PAM). * * @param ctx PAM winbind context * @param user_ret A pointer that holds a pointer to a string * @param unix_username A username * * @return void. */ static void _pam_setup_unix_username(struct pwb_context *ctx, char **user_ret, struct wbcLogonUserInfo *info) { const char *unix_username = NULL; uint32_t i; if (!user_ret || !info) { return; } for (i=0; i < info->num_blobs; i++) { if (strcasecmp(info->blobs[i].name, "unix_username") == 0) { unix_username = (const char *)info->blobs[i].blob.data; break; } } if (!unix_username || !unix_username[0]) { return; } *user_ret = strdup(unix_username); } /** * Set string into the PAM stack. * * @param ctx PAM winbind context. * @param data_name Key name for pam_set_data. * @param value String value. * * @return void. */ static void _pam_set_data_string(struct pwb_context *ctx, const char *data_name, const char *value) { int ret; if (!data_name || !value || (strlen(data_name) == 0) || (strlen(value) == 0)) { return; } ret = pam_set_data(ctx->pamh, data_name, talloc_strdup(NULL, value), _pam_winbind_cleanup_func); if (ret != PAM_SUCCESS) { _pam_log_debug(ctx, LOG_DEBUG, "Could not set data %s: %s\n", data_name, pam_strerror(ctx->pamh, ret)); } } /** * Set info3 strings into the PAM stack. * * @param ctx PAM winbind context. * @param data_name Key name for pam_set_data. * @param value String value. * * @return void. */ static void _pam_set_data_info3(struct pwb_context *ctx, const struct wbcAuthUserInfo *info) { if (info != NULL) { _pam_set_data_string(ctx, PAM_WINBIND_HOMEDIR, info->home_directory); _pam_set_data_string(ctx, PAM_WINBIND_LOGONSCRIPT, info->logon_script); _pam_set_data_string(ctx, PAM_WINBIND_LOGONSERVER, info->logon_server); _pam_set_data_string(ctx, PAM_WINBIND_PROFILEPATH, info->profile_path); } } /** * Free info3 strings in the PAM stack. * * @param pamh PAM handle * * @return void. */ static void _pam_free_data_info3(pam_handle_t *pamh) { pam_set_data(pamh, PAM_WINBIND_HOMEDIR, NULL, NULL); pam_set_data(pamh, PAM_WINBIND_LOGONSCRIPT, NULL, NULL); pam_set_data(pamh, PAM_WINBIND_LOGONSERVER, NULL, NULL); pam_set_data(pamh, PAM_WINBIND_PROFILEPATH, NULL, NULL); } /** * Send PAM_ERROR_MSG for cached or grace logons. * * @param ctx PAM winbind context. * @param username User in PAM request. * @param info3_user_flgs Info3 flags containing logon type bits. * * @return void. */ static void _pam_warn_logon_type(struct pwb_context *ctx, const char *username, uint32_t info3_user_flgs) { /* inform about logon type */ if (PAM_WB_GRACE_LOGON(info3_user_flgs)) { _make_remark(ctx, PAM_ERROR_MSG, _("Grace login. " "Please change your password as soon you're " "online again")); _pam_log_debug(ctx, LOG_DEBUG, "User %s logged on using grace logon\n", username); } else if (PAM_WB_CACHED_LOGON(info3_user_flgs)) { _make_remark(ctx, PAM_ERROR_MSG, _("Domain Controller unreachable, " "using cached credentials instead. " "Network resources may be unavailable")); _pam_log_debug(ctx, LOG_DEBUG, "User %s logged on using cached credentials\n", username); } } /** * Send PAM_ERROR_MSG for krb5 errors. * * @param ctx PAM winbind context. * @param username User in PAM request. * @param info3_user_flgs Info3 flags containing logon type bits. * * @return void. */ static void _pam_warn_krb5_failure(struct pwb_context *ctx, const char *username, uint32_t info3_user_flgs) { if (PAM_WB_KRB5_CLOCK_SKEW(info3_user_flgs)) { _make_remark(ctx, PAM_ERROR_MSG, _("Failed to establish your Kerberos Ticket cache " "due time differences\n" "with the domain controller. " "Please verify the system time.\n")); _pam_log_debug(ctx, LOG_DEBUG, "User %s: Clock skew when getting Krb5 TGT\n", username); } } static bool _pam_check_remark_auth_err(struct pwb_context *ctx, const struct wbcAuthErrorInfo *e, const char *nt_status_string, int *pam_err) { const char *ntstatus = NULL; const char *error_string = NULL; if (!e || !pam_err) { return false; } ntstatus = e->nt_string; if (!ntstatus) { return false; } if (strcasecmp(ntstatus, nt_status_string) == 0) { error_string = _get_ntstatus_error_string(nt_status_string); if (error_string) { _make_remark(ctx, PAM_ERROR_MSG, error_string); *pam_err = e->pam_error; return true; } if (e->display_string) { _make_remark(ctx, PAM_ERROR_MSG, _(e->display_string)); *pam_err = e->pam_error; return true; } _make_remark(ctx, PAM_ERROR_MSG, nt_status_string); *pam_err = e->pam_error; return true; } return false; }; /** * Compose Password Restriction String for a PAM_ERROR_MSG conversation. * * @param i The wbcUserPasswordPolicyInfo struct. * * @return string (caller needs to talloc_free). */ static char *_pam_compose_pwd_restriction_string(struct pwb_context *ctx, struct wbcUserPasswordPolicyInfo *i) { char *str = NULL; if (!i) { goto failed; } str = talloc_asprintf(ctx, _("Your password ")); if (!str) { goto failed; } if (i->min_length_password > 0) { str = talloc_asprintf_append(str, _("must be at least %d characters; "), i->min_length_password); if (!str) { goto failed; } } if (i->password_history > 0) { str = talloc_asprintf_append(str, _("cannot repeat any of your previous %d " "passwords; "), i->password_history); if (!str) { goto failed; } } if (i->password_properties & WBC_DOMAIN_PASSWORD_COMPLEX) { str = talloc_asprintf_append(str, _("must contain capitals, numerals " "or punctuation; " "and cannot contain your account " "or full name; ")); if (!str) { goto failed; } } str = talloc_asprintf_append(str, _("Please type a different password. " "Type a password which meets these requirements in " "both text boxes.")); if (!str) { goto failed; } return str; failed: TALLOC_FREE(str); return NULL; } static int _pam_create_homedir(struct pwb_context *ctx, const char *dirname, mode_t mode) { struct stat sbuf; if (stat(dirname, &sbuf) == 0) { return PAM_SUCCESS; } if (mkdir(dirname, mode) != 0) { _make_remark_format(ctx, PAM_TEXT_INFO, _("Creating directory: %s failed: %s"), dirname, strerror(errno)); _pam_log(ctx, LOG_ERR, "could not create dir: %s (%s)", dirname, strerror(errno)); return PAM_PERM_DENIED; } return PAM_SUCCESS; } static int _pam_chown_homedir(struct pwb_context *ctx, const char *dirname, uid_t uid, gid_t gid) { if (chown(dirname, uid, gid) != 0) { _pam_log(ctx, LOG_ERR, "failed to chown user homedir: %s (%s)", dirname, strerror(errno)); return PAM_PERM_DENIED; } return PAM_SUCCESS; } static int _pam_mkhomedir(struct pwb_context *ctx) { struct passwd *pwd = NULL; char *token = NULL; char *create_dir = NULL; char *user_dir = NULL; int ret; const char *username; mode_t mode = 0700; char *safe_ptr = NULL; char *p = NULL; /* Get the username */ ret = pam_get_user(ctx->pamh, &username, NULL); if ((ret != PAM_SUCCESS) || (!username)) { _pam_log_debug(ctx, LOG_DEBUG, "can not get the username"); return PAM_SERVICE_ERR; } pwd = getpwnam(username); if (pwd == NULL) { _pam_log_debug(ctx, LOG_DEBUG, "can not get the username"); return PAM_USER_UNKNOWN; } _pam_log_debug(ctx, LOG_DEBUG, "homedir is: %s", pwd->pw_dir); ret = _pam_create_homedir(ctx, pwd->pw_dir, 0700); if (ret == PAM_SUCCESS) { ret = _pam_chown_homedir(ctx, pwd->pw_dir, pwd->pw_uid, pwd->pw_gid); } if (ret == PAM_SUCCESS) { return ret; } /* maybe we need to create parent dirs */ create_dir = talloc_strdup(ctx, "/"); if (!create_dir) { return PAM_BUF_ERR; } /* find final directory */ user_dir = strrchr(pwd->pw_dir, '/'); if (!user_dir) { return PAM_BUF_ERR; } user_dir++; _pam_log(ctx, LOG_DEBUG, "final directory: %s", user_dir); p = pwd->pw_dir; while ((token = strtok_r(p, "/", &safe_ptr)) != NULL) { mode = 0755; p = NULL; _pam_log_debug(ctx, LOG_DEBUG, "token is %s", token); create_dir = talloc_asprintf_append(create_dir, "%s/", token); if (!create_dir) { return PAM_BUF_ERR; } _pam_log_debug(ctx, LOG_DEBUG, "current_dir is %s", create_dir); if (strcmp(token, user_dir) == 0) { _pam_log_debug(ctx, LOG_DEBUG, "assuming last directory: %s", token); mode = 0700; } ret = _pam_create_homedir(ctx, create_dir, mode); if (ret != PAM_SUCCESS) { return ret; } } return _pam_chown_homedir(ctx, create_dir, pwd->pw_uid, pwd->pw_gid); } /* talk to winbindd */ static int winbind_auth_request(struct pwb_context *ctx, const char *user, const char *pass, const char *member, const char *cctype, const int warn_pwd_expire, struct wbcAuthErrorInfo **p_error, struct wbcLogonUserInfo **p_info, time_t *pwd_last_set, char **user_ret) { wbcErr wbc_status; struct wbcLogonUserParams logon; char membership_of[1024]; uid_t user_uid = -1; uint32_t flags = WBFLAG_PAM_INFO3_TEXT; struct wbcLogonUserInfo *info = NULL; struct wbcAuthUserInfo *user_info = NULL; struct wbcAuthErrorInfo *error = NULL; int ret = PAM_AUTH_ERR; int i; const char *codes[] = { "NT_STATUS_PASSWORD_EXPIRED", "NT_STATUS_PASSWORD_MUST_CHANGE", "NT_STATUS_INVALID_WORKSTATION", "NT_STATUS_INVALID_LOGON_HOURS", "NT_STATUS_ACCOUNT_EXPIRED", "NT_STATUS_ACCOUNT_DISABLED", "NT_STATUS_ACCOUNT_LOCKED_OUT", "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT", "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT", "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT", "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", "NT_STATUS_NO_LOGON_SERVERS", "NT_STATUS_WRONG_PASSWORD", "NT_STATUS_ACCESS_DENIED" }; if (pwd_last_set) { *pwd_last_set = 0; } /* Krb5 auth always has to go against the KDC of the user's realm */ if (ctx->ctrl & WINBIND_KRB5_AUTH) { flags |= WBFLAG_PAM_CONTACT_TRUSTDOM; } if (ctx->ctrl & (WINBIND_KRB5_AUTH|WINBIND_CACHED_LOGIN)) { struct passwd *pwd = NULL; pwd = getpwnam(user); if (pwd == NULL) { return PAM_USER_UNKNOWN; } user_uid = pwd->pw_uid; } if (ctx->ctrl & WINBIND_KRB5_AUTH) { _pam_log_debug(ctx, LOG_DEBUG, "enabling krb5 login flag\n"); flags |= WBFLAG_PAM_KRB5 | WBFLAG_PAM_FALLBACK_AFTER_KRB5; } if (ctx->ctrl & WINBIND_CACHED_LOGIN) { _pam_log_debug(ctx, LOG_DEBUG, "enabling cached login flag\n"); flags |= WBFLAG_PAM_CACHED_LOGIN; } if (user_ret) { *user_ret = NULL; flags |= WBFLAG_PAM_UNIX_NAME; } if (cctype != NULL) { _pam_log_debug(ctx, LOG_DEBUG, "enabling request for a %s krb5 ccache\n", cctype); } if (member != NULL) { ZERO_STRUCT(membership_of); if (!winbind_name_list_to_sid_string_list(ctx, user, member, membership_of, sizeof(membership_of))) { _pam_log_debug(ctx, LOG_ERR, "failed to serialize membership of sid " "\"%s\"\n", member); return PAM_AUTH_ERR; } } ZERO_STRUCT(logon); logon.username = user; logon.password = pass; if (cctype) { wbc_status = wbcAddNamedBlob(&logon.num_blobs, &logon.blobs, "krb5_cc_type", 0, discard_const_p(uint8_t, cctype), strlen(cctype)+1); if (!WBC_ERROR_IS_OK(wbc_status)) { goto done; } } wbc_status = wbcAddNamedBlob(&logon.num_blobs, &logon.blobs, "flags", 0, (uint8_t *)&flags, sizeof(flags)); if (!WBC_ERROR_IS_OK(wbc_status)) { goto done; } wbc_status = wbcAddNamedBlob(&logon.num_blobs, &logon.blobs, "user_uid", 0, (uint8_t *)&user_uid, sizeof(user_uid)); if (!WBC_ERROR_IS_OK(wbc_status)) { goto done; } if (member) { wbc_status = wbcAddNamedBlob(&logon.num_blobs, &logon.blobs, "membership_of", 0, (uint8_t *)membership_of, sizeof(membership_of)); if (!WBC_ERROR_IS_OK(wbc_status)) { goto done; } } wbc_status = wbcCtxLogonUser(ctx->wbc_ctx, &logon, &info, &error, NULL); ret = wbc_auth_error_to_pam_error(ctx, error, wbc_status, user, "wbcLogonUser"); wbcFreeMemory(logon.blobs); logon.blobs = NULL; if (info && info->info) { user_info = info->info; } if (pwd_last_set && user_info) { *pwd_last_set = user_info->pass_last_set_time; } if (p_info && info) { *p_info = info; } if (p_error && error) { /* We want to process the error in the caller. */ *p_error = error; return ret; } for (i=0; ipass_last_set_time; SMB_TIME_T must_set = user_info->pass_must_change_time; _pam_log_debug(ctx, LOG_DEBUG, "Password has expired " "(Password was last set: %lld, " "it must be changed here " "%lld (now it's: %ld))\n", (long long int)last_set, (long long int)must_set, (long)time(NULL)); return PAM_AUTHTOK_EXPIRED; } if (change_pwd) { ret = PAM_NEW_AUTHTOK_REQD; goto done; } /* inform about logon type */ _pam_warn_logon_type(ctx, user, user_info->user_flags); /* inform about krb5 failures */ _pam_warn_krb5_failure(ctx, user, user_info->user_flags); /* set some info3 info for other modules in the stack */ _pam_set_data_info3(ctx, user_info); /* put krb5ccname into env */ _pam_setup_krb5_env(ctx, info); /* If winbindd returned a username, return the pointer to it * here. */ _pam_setup_unix_username(ctx, user_ret, info); } done: wbcFreeMemory(logon.blobs); if (info && info->blobs && !p_info) { wbcFreeMemory(info->blobs); /* * We set blobs to NULL to prevent a use after free in the * in the wbcLogonUserInfoDestructor */ info->blobs = NULL; } if (error && !p_error) { wbcFreeMemory(error); } if (info && !p_info) { wbcFreeMemory(info); } return ret; } /* talk to winbindd */ static int winbind_chauthtok_request(struct pwb_context *ctx, const char *user, const char *oldpass, const char *newpass, time_t pwd_last_set) { wbcErr wbc_status; struct wbcChangePasswordParams params; struct wbcAuthErrorInfo *error = NULL; struct wbcUserPasswordPolicyInfo *policy = NULL; enum wbcPasswordChangeRejectReason reject_reason = -1; uint32_t flags = 0; int i; const char *codes[] = { "NT_STATUS_BACKUP_CONTROLLER", "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", "NT_STATUS_NO_LOGON_SERVERS", "NT_STATUS_ACCESS_DENIED", "NT_STATUS_PWD_TOO_SHORT", /* TODO: tell the min pwd length ? */ "NT_STATUS_PWD_TOO_RECENT", /* TODO: tell the minage ? */ "NT_STATUS_PWD_HISTORY_CONFLICT" /* TODO: tell the history length ? */ }; int ret = PAM_AUTH_ERR; ZERO_STRUCT(params); if (ctx->ctrl & WINBIND_KRB5_AUTH) { flags |= WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM; } if (ctx->ctrl & WINBIND_CACHED_LOGIN) { flags |= WBFLAG_PAM_CACHED_LOGIN; } params.account_name = user; params.level = WBC_CHANGE_PASSWORD_LEVEL_PLAIN; params.old_password.plaintext = oldpass; params.new_password.plaintext = newpass; params.flags = flags; wbc_status = wbcCtxChangeUserPasswordEx(ctx->wbc_ctx, ¶ms, &error, &reject_reason, &policy); ret = wbc_auth_error_to_pam_error(ctx, error, wbc_status, user, "wbcChangeUserPasswordEx"); if (WBC_ERROR_IS_OK(wbc_status)) { _pam_log(ctx, LOG_NOTICE, "user '%s' password changed", user); return PAM_SUCCESS; } if (!error) { wbcFreeMemory(policy); return ret; } for (i=0; int_string, "NT_STATUS_PASSWORD_RESTRICTION")) { char *pwd_restriction_string = NULL; SMB_TIME_T min_pwd_age = 0; if (policy) { min_pwd_age = policy->min_passwordage; } /* FIXME: avoid to send multiple PAM messages after another */ switch ((int)reject_reason) { case -1: break; case WBC_PWD_CHANGE_NO_ERROR: if ((min_pwd_age > 0) && (pwd_last_set + min_pwd_age > time(NULL))) { PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PWD_TOO_RECENT"); } break; case WBC_PWD_CHANGE_PASSWORD_TOO_SHORT: PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PWD_TOO_SHORT"); break; case WBC_PWD_CHANGE_PWD_IN_HISTORY: PAM_WB_REMARK_DIRECT(ctx, "NT_STATUS_PWD_HISTORY_CONFLICT"); break; case WBC_PWD_CHANGE_NOT_COMPLEX: _make_remark(ctx, PAM_ERROR_MSG, _("Password does not meet " "complexity requirements")); break; default: _pam_log_debug(ctx, LOG_DEBUG, "unknown password change " "reject reason: %d", reject_reason); break; } pwd_restriction_string = _pam_compose_pwd_restriction_string(ctx, policy); if (pwd_restriction_string) { _make_remark(ctx, PAM_ERROR_MSG, pwd_restriction_string); TALLOC_FREE(pwd_restriction_string); } } done: wbcFreeMemory(error); wbcFreeMemory(policy); return ret; } /* * Checks if a user has an account * * return values: * 1 = User not found * 0 = OK * -1 = System error */ static int valid_user(struct pwb_context *ctx, const char *user) { /* check not only if the user is available over NSS calls, also make * sure it's really a winbind user, this is important when stacking PAM * modules in the 'account' or 'password' facility. */ wbcErr wbc_status; struct passwd *pwd = NULL; struct passwd *wb_pwd = NULL; pwd = getpwnam(user); if (pwd == NULL) { return 1; } wbc_status = wbcCtxGetpwnam(ctx->wbc_ctx, user, &wb_pwd); wbcFreeMemory(wb_pwd); if (!WBC_ERROR_IS_OK(wbc_status)) { _pam_log(ctx, LOG_DEBUG, "valid_user: wbcGetpwnam gave %s\n", wbcErrorString(wbc_status)); } switch (wbc_status) { case WBC_ERR_UNKNOWN_USER: /* match other insane libwbclient return codes */ case WBC_ERR_WINBIND_NOT_AVAILABLE: case WBC_ERR_DOMAIN_NOT_FOUND: return 1; case WBC_ERR_SUCCESS: return 0; default: break; } return -1; } static char *_pam_delete(register char *xx) { _pam_overwrite(xx); _pam_drop(xx); return NULL; } /* * obtain a password from the user */ static int _winbind_read_password(struct pwb_context *ctx, unsigned int ctrl, const char *comment, const char *prompt1, const char *prompt2, const char **pass) { int authtok_flag; int retval; const char *item; char *token; _pam_log(ctx, LOG_DEBUG, "getting password (0x%08x)", ctrl); /* * make sure nothing inappropriate gets returned */ *pass = token = NULL; /* * which authentication token are we getting? */ if (on(WINBIND__OLD_PASSWORD, ctrl)) { authtok_flag = PAM_OLDAUTHTOK; } else { authtok_flag = PAM_AUTHTOK; } /* * should we obtain the password from a PAM item ? */ if (on(WINBIND_TRY_FIRST_PASS_ARG, ctrl) || on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { retval = pam_get_item(ctx->pamh, authtok_flag, (const void **) &item); if (retval != PAM_SUCCESS) { /* very strange. */ _pam_log(ctx, LOG_ALERT, "pam_get_item returned error " "to unix-read-password"); return retval; } else if (item != NULL) { /* we have a password! */ *pass = item; item = NULL; _pam_log(ctx, LOG_DEBUG, "pam_get_item returned a password"); return PAM_SUCCESS; } else if (on(WINBIND_USE_FIRST_PASS_ARG, ctrl)) { return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ } else if (on(WINBIND_USE_AUTHTOK_ARG, ctrl) && off(WINBIND__OLD_PASSWORD, ctrl)) { return PAM_AUTHTOK_RECOVER_ERR; } } /* * getting here implies we will have to get the password from the * user directly. */ { struct pam_message msg[3]; const struct pam_message *pmsg[3]; struct pam_response *resp; int i, replies; /* prepare to converse */ if (comment != NULL && off(ctrl, WINBIND_SILENT)) { pmsg[0] = &msg[0]; msg[0].msg_style = PAM_TEXT_INFO; msg[0].msg = discard_const_p(char, comment); i = 1; } else { i = 0; } pmsg[i] = &msg[i]; msg[i].msg_style = PAM_PROMPT_ECHO_OFF; msg[i++].msg = discard_const_p(char, prompt1); replies = 1; if (prompt2 != NULL) { pmsg[i] = &msg[i]; msg[i].msg_style = PAM_PROMPT_ECHO_OFF; msg[i++].msg = discard_const_p(char, prompt2); ++replies; } /* so call the conversation expecting i responses */ resp = NULL; retval = converse(ctx->pamh, i, pmsg, &resp); if (resp == NULL) { if (retval == PAM_SUCCESS) { retval = PAM_AUTHTOK_RECOVER_ERR; } goto done; } if (retval != PAM_SUCCESS) { _pam_drop_reply(resp, i); goto done; } /* interpret the response */ token = x_strdup(resp[i - replies].resp); if (!token) { _pam_log(ctx, LOG_NOTICE, "could not recover " "authentication token"); retval = PAM_AUTHTOK_RECOVER_ERR; goto done; } if (replies == 2) { /* verify that password entered correctly */ if (!resp[i - 1].resp || strcmp(token, resp[i - 1].resp)) { _pam_delete(token); /* mistyped */ retval = PAM_AUTHTOK_RECOVER_ERR; _make_remark(ctx, PAM_ERROR_MSG, MISTYPED_PASS); } } /* * tidy up the conversation (resp_retcode) is ignored * -- what is it for anyway? AGM */ _pam_drop_reply(resp, i); } done: if (retval != PAM_SUCCESS) { _pam_log_debug(ctx, LOG_DEBUG, "unable to obtain a password"); return retval; } /* 'token' is the entered password */ /* we store this password as an item */ retval = pam_set_item(ctx->pamh, authtok_flag, token); _pam_delete(token); /* clean it up */ if (retval != PAM_SUCCESS || (retval = pam_get_item(ctx->pamh, authtok_flag, (const void **) &item)) != PAM_SUCCESS) { _pam_log(ctx, LOG_CRIT, "error manipulating password"); return retval; } *pass = item; item = NULL; /* break link to password */ return PAM_SUCCESS; } static const char *get_conf_item_string(struct pwb_context *ctx, const char *item, int config_flag) { int i = 0; const char *parm_opt = NULL; if (!(ctx->ctrl & config_flag)) { goto out; } /* let the pam opt take precedence over the pam_winbind.conf option */ for (i=0; iargc; i++) { if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) { char *p; if ((p = strchr(ctx->argv[i], '=')) == NULL) { _pam_log(ctx, LOG_INFO, "no \"=\" delimiter for \"%s\" found\n", item); goto out; } _pam_log_debug(ctx, LOG_INFO, "PAM config: %s '%s'\n", item, p+1); return p + 1; } } if (ctx->dict) { char *key = NULL; key = talloc_asprintf(ctx, "global:%s", item); if (!key) { goto out; } parm_opt = tiniparser_getstring_nonempty(ctx->dict, key, NULL); TALLOC_FREE(key); _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%s'\n", item, parm_opt); } out: return parm_opt; } static int get_config_item_int(struct pwb_context *ctx, const char *item, int config_flag) { int i, parm_opt = -1; if (!(ctx->ctrl & config_flag)) { goto out; } /* let the pam opt take precedence over the pam_winbind.conf option */ for (i = 0; i < ctx->argc; i++) { if ((strncmp(ctx->argv[i], item, strlen(item)) == 0)) { char *p; if ((p = strchr(ctx->argv[i], '=')) == NULL) { _pam_log(ctx, LOG_INFO, "no \"=\" delimiter for \"%s\" found\n", item); goto out; } parm_opt = atoi(p + 1); _pam_log_debug(ctx, LOG_INFO, "PAM config: %s '%d'\n", item, parm_opt); return parm_opt; } } if (ctx->dict) { char *key = NULL; key = talloc_asprintf(ctx, "global:%s", item); if (!key) { goto out; } parm_opt = tiniparser_getint(ctx->dict, key, -1); TALLOC_FREE(key); _pam_log_debug(ctx, LOG_INFO, "CONFIG file: %s '%d'\n", item, parm_opt); } out: return parm_opt; } static const char *get_krb5_cc_type_from_config(struct pwb_context *ctx) { return get_conf_item_string(ctx, "krb5_ccache_type", WINBIND_KRB5_CCACHE_TYPE); } static const char *get_member_from_config(struct pwb_context *ctx) { const char *ret = NULL; ret = get_conf_item_string(ctx, "require_membership_of", WINBIND_REQUIRED_MEMBERSHIP); if (ret != NULL) { return ret; } return get_conf_item_string(ctx, "require-membership-of", WINBIND_REQUIRED_MEMBERSHIP); } static int get_warn_pwd_expire_from_config(struct pwb_context *ctx) { int ret; ret = get_config_item_int(ctx, "warn_pwd_expire", WINBIND_WARN_PWD_EXPIRE); /* no or broken setting */ if (ret < 0) { return DEFAULT_DAYS_TO_WARN_BEFORE_PWD_EXPIRES; } return ret; } /** * Retrieve the winbind separator. * * @param ctx PAM winbind context. * * @return string separator character. NULL on failure. */ static char winbind_get_separator(struct pwb_context *ctx) { wbcErr wbc_status; static struct wbcInterfaceDetails *details = NULL; wbc_status = wbcCtxInterfaceDetails(ctx->wbc_ctx, &details); if (!WBC_ERROR_IS_OK(wbc_status)) { _pam_log(ctx, LOG_ERR, "Could not retrieve winbind interface details: %s", wbcErrorString(wbc_status)); return '\0'; } if (!details) { return '\0'; } return details->winbind_separator; } /** * Convert a upn to a name. * * @param ctx PAM winbind context. * @param upn User UPN to be translated. * * @return converted name. NULL pointer on failure. Caller needs to free. */ static char* winbind_upn_to_username(struct pwb_context *ctx, const char *upn) { char sep; wbcErr wbc_status = WBC_ERR_UNKNOWN_FAILURE; struct wbcDomainSid sid; enum wbcSidType type; char *domain = NULL; char *name; char *p; char *result; /* This cannot work when the winbind separator = @ */ sep = winbind_get_separator(ctx); if (!sep || sep == '@') { return NULL; } name = talloc_strdup(ctx, upn); if (!name) { return NULL; } p = strchr(name, '@'); if (p == NULL) { TALLOC_FREE(name); return NULL; } *p = '\0'; domain = p + 1; /* Convert the UPN to a SID */ wbc_status = wbcCtxLookupName(ctx->wbc_ctx, domain, name, &sid, &type); if (!WBC_ERROR_IS_OK(wbc_status)) { return NULL; } /* Convert the the SID back to the sAMAccountName */ wbc_status = wbcCtxLookupSid(ctx->wbc_ctx, &sid, &domain, &name, &type); if (!WBC_ERROR_IS_OK(wbc_status)) { return NULL; } result = talloc_asprintf(ctx, "%s%c%s", domain, sep, name); wbcFreeMemory(domain); wbcFreeMemory(name); return result; } static int _pam_delete_cred(pam_handle_t *pamh, int flags, int argc, enum pam_winbind_request_type type, const char **argv) { int retval = PAM_SUCCESS; struct pwb_context *ctx = NULL; struct wbcLogoffUserParams logoff; struct wbcAuthErrorInfo *error = NULL; const char *user; wbcErr wbc_status = WBC_ERR_SUCCESS; ZERO_STRUCT(logoff); retval = _pam_winbind_init_context(pamh, flags, argc, argv, type, &ctx); if (retval != PAM_SUCCESS) { return retval; } _PAM_LOG_FUNCTION_ENTER("_pam_delete_cred", ctx); if (ctx->ctrl & WINBIND_KRB5_AUTH) { /* destroy the ccache here */ uint32_t wbc_flags = 0; const char *ccname = NULL; struct passwd *pwd = NULL; retval = pam_get_user(pamh, &user, _("Username: ")); if (retval != PAM_SUCCESS) { _pam_log(ctx, LOG_ERR, "could not identify user"); goto out; } if (user == NULL) { _pam_log(ctx, LOG_ERR, "username was NULL!"); retval = PAM_USER_UNKNOWN; goto out; } _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user); ccname = pam_getenv(pamh, "KRB5CCNAME"); if (ccname == NULL) { _pam_log_debug(ctx, LOG_DEBUG, "user has no KRB5CCNAME environment"); } pwd = getpwnam(user); if (pwd == NULL) { retval = PAM_USER_UNKNOWN; goto out; } wbc_flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM; logoff.username = user; if (ccname) { wbc_status = wbcAddNamedBlob(&logoff.num_blobs, &logoff.blobs, "ccfilename", 0, discard_const_p(uint8_t, ccname), strlen(ccname)+1); if (!WBC_ERROR_IS_OK(wbc_status)) { goto out; } } wbc_status = wbcAddNamedBlob(&logoff.num_blobs, &logoff.blobs, "flags", 0, (uint8_t *)&wbc_flags, sizeof(wbc_flags)); if (!WBC_ERROR_IS_OK(wbc_status)) { goto out; } wbc_status = wbcAddNamedBlob(&logoff.num_blobs, &logoff.blobs, "user_uid", 0, (uint8_t *)&pwd->pw_uid, sizeof(pwd->pw_uid)); if (!WBC_ERROR_IS_OK(wbc_status)) { goto out; } wbc_status = wbcCtxLogoffUserEx(ctx->wbc_ctx, &logoff, &error); retval = wbc_auth_error_to_pam_error(ctx, error, wbc_status, user, "wbcLogoffUser"); wbcFreeMemory(error); wbcFreeMemory(logoff.blobs); logoff.blobs = NULL; if (!WBC_ERROR_IS_OK(wbc_status)) { _pam_log(ctx, LOG_INFO, "failed to logoff user %s: %s\n", user, wbcErrorString(wbc_status)); } } out: if (logoff.blobs) { wbcFreeMemory(logoff.blobs); } if (!WBC_ERROR_IS_OK(wbc_status)) { retval = wbc_auth_error_to_pam_error(ctx, error, wbc_status, user, "wbcLogoffUser"); } /* * Delete the krb5 ccname variable from the PAM environment * if it was set by winbind. */ if ((ctx->ctrl & WINBIND_KRB5_AUTH) && pam_getenv(pamh, "KRB5CCNAME")) { pam_putenv(pamh, "KRB5CCNAME"); } _PAM_LOG_FUNCTION_LEAVE("_pam_delete_cred", ctx, retval); TALLOC_FREE(ctx); return retval; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *username; const char *password; const char *member = NULL; const char *cctype = NULL; int warn_pwd_expire; int retval = PAM_AUTH_ERR; char *username_ret = NULL; char *new_authtok_required = NULL; char *real_username = NULL; struct pwb_context *ctx = NULL; retval = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_AUTHENTICATE, &ctx); if (retval != PAM_SUCCESS) { return retval; } _PAM_LOG_FUNCTION_ENTER("pam_sm_authenticate", ctx); /* Get the username */ retval = pam_get_user(pamh, &username, NULL); if ((retval != PAM_SUCCESS) || (!username)) { _pam_log_debug(ctx, LOG_DEBUG, "can not get the username"); retval = PAM_SERVICE_ERR; goto out; } #if defined(AIX) /* Decode the user name since AIX does not support logn user names by default. The name is encoded as _#uid. */ if (username[0] == '_') { uid_t id = atoi(&username[1]); struct passwd *pw = NULL; if ((id!=0) && ((pw = getpwuid(id)) != NULL)) { real_username = strdup(pw->pw_name); } } #endif if (!real_username) { /* Just making a copy of the username we got from PAM */ if ((real_username = strdup(username)) == NULL) { _pam_log_debug(ctx, LOG_DEBUG, "memory allocation failure when copying " "username"); retval = PAM_SERVICE_ERR; goto out; } } /* Maybe this was a UPN */ if (strchr(real_username, '@') != NULL) { char *samaccountname = NULL; samaccountname = winbind_upn_to_username(ctx, real_username); if (samaccountname) { free(real_username); real_username = strdup(samaccountname); } } retval = _winbind_read_password(ctx, ctx->ctrl, NULL, _("Password: "), NULL, &password); if (retval != PAM_SUCCESS) { _pam_log(ctx, LOG_ERR, "Could not retrieve user's password"); retval = PAM_AUTHTOK_ERR; goto out; } /* Let's not give too much away in the log file */ #ifdef DEBUG_PASSWORD _pam_log_debug(ctx, LOG_INFO, "Verify user '%s' with password '%s'", real_username, password); #else _pam_log_debug(ctx, LOG_INFO, "Verify user '%s'", real_username); #endif member = get_member_from_config(ctx); cctype = get_krb5_cc_type_from_config(ctx); warn_pwd_expire = get_warn_pwd_expire_from_config(ctx); /* Now use the username to look up password */ retval = winbind_auth_request(ctx, real_username, password, member, cctype, warn_pwd_expire, NULL, NULL, NULL, &username_ret); if (retval == PAM_NEW_AUTHTOK_REQD || retval == PAM_AUTHTOK_EXPIRED) { char *new_authtok_required_during_auth = NULL; new_authtok_required = talloc_asprintf(NULL, "%d", retval); if (!new_authtok_required) { retval = PAM_BUF_ERR; goto out; } pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, new_authtok_required, _pam_winbind_cleanup_func); retval = PAM_SUCCESS; new_authtok_required_during_auth = talloc_asprintf(NULL, "%d", true); if (!new_authtok_required_during_auth) { retval = PAM_BUF_ERR; goto out; } pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, new_authtok_required_during_auth, _pam_winbind_cleanup_func); goto out; } out: if (username_ret) { pam_set_item (pamh, PAM_USER, username_ret); _pam_log_debug(ctx, LOG_INFO, "Returned user was '%s'", username_ret); free(username_ret); } if (real_username) { free(real_username); } if (!new_authtok_required) { pam_set_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, NULL, NULL); } if (retval != PAM_SUCCESS) { _pam_free_data_info3(pamh); } _PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval); TALLOC_FREE(ctx); return retval; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { int ret = PAM_SYSTEM_ERR; struct pwb_context *ctx = NULL; ret = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_SETCRED, &ctx); if (ret != PAM_SUCCESS) { return ret; } _PAM_LOG_FUNCTION_ENTER("pam_sm_setcred", ctx); switch (flags & ~PAM_SILENT) { case PAM_DELETE_CRED: ret = _pam_delete_cred(pamh, flags, argc, PAM_WINBIND_SETCRED, argv); break; case PAM_REFRESH_CRED: _pam_log_debug(ctx, LOG_WARNING, "PAM_REFRESH_CRED not implemented"); ret = PAM_SUCCESS; break; case PAM_REINITIALIZE_CRED: _pam_log_debug(ctx, LOG_WARNING, "PAM_REINITIALIZE_CRED not implemented"); ret = PAM_SUCCESS; break; case PAM_ESTABLISH_CRED: _pam_log_debug(ctx, LOG_WARNING, "PAM_ESTABLISH_CRED not implemented"); ret = PAM_SUCCESS; break; default: ret = PAM_SYSTEM_ERR; break; } _PAM_LOG_FUNCTION_LEAVE("pam_sm_setcred", ctx, ret); TALLOC_FREE(ctx); return ret; } /* * Account management. We want to verify that the account exists * before returning PAM_SUCCESS */ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *username; int ret = PAM_USER_UNKNOWN; const char *tmp = NULL; struct pwb_context *ctx = NULL; ret = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_ACCT_MGMT, &ctx); if (ret != PAM_SUCCESS) { return ret; } _PAM_LOG_FUNCTION_ENTER("pam_sm_acct_mgmt", ctx); /* Get the username */ ret = pam_get_user(pamh, &username, NULL); if ((ret != PAM_SUCCESS) || (!username)) { _pam_log_debug(ctx, LOG_DEBUG, "can not get the username"); ret = PAM_SERVICE_ERR; goto out; } /* Verify the username */ ret = valid_user(ctx, username); switch (ret) { case -1: /* some sort of system error. The log was already printed */ ret = PAM_SERVICE_ERR; goto out; case 1: /* the user does not exist */ _pam_log_debug(ctx, LOG_NOTICE, "user '%s' not found", username); if (ctx->ctrl & WINBIND_UNKNOWN_OK_ARG) { ret = PAM_IGNORE; goto out; } ret = PAM_USER_UNKNOWN; goto out; case 0: pam_get_data(pamh, PAM_WINBIND_NEW_AUTHTOK_REQD, (const void **)&tmp); if (tmp != NULL) { ret = atoi(tmp); switch (ret) { case PAM_AUTHTOK_EXPIRED: /* Since new token is required in this case */ FALL_THROUGH; case PAM_NEW_AUTHTOK_REQD: _pam_log(ctx, LOG_WARNING, "pam_sm_acct_mgmt success but %s is set", PAM_WINBIND_NEW_AUTHTOK_REQD); _pam_log(ctx, LOG_NOTICE, "user '%s' needs new password", username); /* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */ ret = PAM_NEW_AUTHTOK_REQD; goto out; default: _pam_log(ctx, LOG_WARNING, "pam_sm_acct_mgmt success"); _pam_log(ctx, LOG_NOTICE, "user '%s' granted access", username); ret = PAM_SUCCESS; goto out; } } /* Otherwise, the authentication looked good */ _pam_log(ctx, LOG_NOTICE, "user '%s' granted access", username); ret = PAM_SUCCESS; goto out; default: /* we don't know anything about this return value */ _pam_log(ctx, LOG_ERR, "internal module error (ret = %d, user = '%s')", ret, username); ret = PAM_SERVICE_ERR; goto out; } /* should not be reached */ ret = PAM_IGNORE; out: _PAM_LOG_FUNCTION_LEAVE("pam_sm_acct_mgmt", ctx, ret); TALLOC_FREE(ctx); return ret; } PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int ret = PAM_SUCCESS; struct pwb_context *ctx = NULL; ret = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_OPEN_SESSION, &ctx); if (ret != PAM_SUCCESS) { return ret; } _PAM_LOG_FUNCTION_ENTER("pam_sm_open_session", ctx); if (ctx->ctrl & WINBIND_MKHOMEDIR) { /* check and create homedir */ ret = _pam_mkhomedir(ctx); } _PAM_LOG_FUNCTION_LEAVE("pam_sm_open_session", ctx, ret); TALLOC_FREE(ctx); return ret; } PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int ret = PAM_SUCCESS; struct pwb_context *ctx = NULL; ret = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_CLOSE_SESSION, &ctx); if (ret != PAM_SUCCESS) { return ret; } _PAM_LOG_FUNCTION_ENTER("pam_sm_close_session", ctx); _PAM_LOG_FUNCTION_LEAVE("pam_sm_close_session", ctx, ret); TALLOC_FREE(ctx); return ret; } /** * evaluate whether we need to re-authenticate with kerberos after a * password change * * @param ctx PAM winbind context. * @param user The username * * @return boolean Returns true if required, false if not. */ static bool _pam_require_krb5_auth_after_chauthtok(struct pwb_context *ctx, const char *user) { /* Make sure that we only do this if a) the chauthtok got initiated * during a logon attempt (authenticate->acct_mgmt->chauthtok) b) any * later password change via the "passwd" command if done by the user * itself * NB. If we login from gdm or xdm and the password expires, * we change the password, but there is no memory cache. * Thus, even for passthrough login, we should do the * authentication again to update memory cache. * --- BoYang * */ const char *new_authtok_reqd_during_auth = NULL; struct passwd *pwd = NULL; pam_get_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, (const void **) &new_authtok_reqd_during_auth); pam_set_data(ctx->pamh, PAM_WINBIND_NEW_AUTHTOK_REQD_DURING_AUTH, NULL, NULL); if (new_authtok_reqd_during_auth) { return true; } pwd = getpwnam(user); if (!pwd) { return false; } if (getuid() == pwd->pw_uid) { return true; } return false; } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv) { unsigned int lctrl; int ret; bool cached_login = false; /* */ const char *user; const char *pass_old; const char *pass_new; /* */ char *Announce; int retry = 0; char *username_ret = NULL; struct wbcAuthErrorInfo *error = NULL; struct pwb_context *ctx = NULL; ret = _pam_winbind_init_context(pamh, flags, argc, argv, PAM_WINBIND_CHAUTHTOK, &ctx); if (ret != PAM_SUCCESS) { return ret; } _PAM_LOG_FUNCTION_ENTER("pam_sm_chauthtok", ctx); cached_login = (ctx->ctrl & WINBIND_CACHED_LOGIN); /* clearing offline bit for auth */ ctx->ctrl &= ~WINBIND_CACHED_LOGIN; /* * First get the name of a user */ ret = pam_get_user(pamh, &user, _("Username: ")); if (ret != PAM_SUCCESS) { _pam_log(ctx, LOG_ERR, "password - could not identify user"); goto out; } if (user == NULL) { _pam_log(ctx, LOG_ERR, "username was NULL!"); ret = PAM_USER_UNKNOWN; goto out; } _pam_log_debug(ctx, LOG_DEBUG, "username [%s] obtained", user); /* check if this is really a user in winbindd, not only in NSS */ ret = valid_user(ctx, user); switch (ret) { case 1: ret = PAM_USER_UNKNOWN; goto out; case -1: ret = PAM_SYSTEM_ERR; goto out; default: break; } /* * obtain and verify the current password (OLDAUTHTOK) for * the user. */ if (flags & PAM_PRELIM_CHECK) { time_t pwdlastset_prelim = 0; /* instruct user what is happening */ #define greeting _("Changing password for") Announce = talloc_asprintf(ctx, "%s %s", greeting, user); if (!Announce) { _pam_log(ctx, LOG_CRIT, "password - out of memory"); ret = PAM_BUF_ERR; goto out; } #undef greeting lctrl = ctx->ctrl | WINBIND__OLD_PASSWORD; ret = _winbind_read_password(ctx, lctrl, Announce, _("(current) NT password: "), NULL, (const char **) &pass_old); TALLOC_FREE(Announce); if (ret != PAM_SUCCESS) { _pam_log(ctx, LOG_NOTICE, "password - (old) token not obtained"); goto out; } /* verify that this is the password for this user */ ret = winbind_auth_request(ctx, user, pass_old, NULL, NULL, 0, &error, NULL, &pwdlastset_prelim, NULL); if (ret != PAM_ACCT_EXPIRED && ret != PAM_AUTHTOK_EXPIRED && ret != PAM_NEW_AUTHTOK_REQD && ret != PAM_SUCCESS) { pass_old = NULL; goto out; } pam_set_data(pamh, PAM_WINBIND_PWD_LAST_SET, (void *)pwdlastset_prelim, NULL); ret = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); pass_old = NULL; if (ret != PAM_SUCCESS) { _pam_log(ctx, LOG_CRIT, "failed to set PAM_OLDAUTHTOK"); } } else if (flags & PAM_UPDATE_AUTHTOK) { time_t pwdlastset_update = 0; /* * obtain the proposed password */ /* * get the old token back. */ ret = pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **) &pass_old); if (ret != PAM_SUCCESS) { _pam_log(ctx, LOG_NOTICE, "user not authenticated"); goto out; } lctrl = ctx->ctrl & ~WINBIND_TRY_FIRST_PASS_ARG; if (on(WINBIND_USE_AUTHTOK_ARG, lctrl)) { lctrl |= WINBIND_USE_FIRST_PASS_ARG; } if (on(WINBIND_TRY_AUTHTOK_ARG, lctrl)) { lctrl |= WINBIND_TRY_FIRST_PASS_ARG; } retry = 0; ret = PAM_AUTHTOK_ERR; while ((ret != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { /* * use_authtok is to force the use of a previously entered * password -- needed for pluggable password strength checking */ ret = _winbind_read_password(ctx, lctrl, NULL, _("Enter new NT password: "), _("Retype new NT password: "), (const char **)&pass_new); if (ret != PAM_SUCCESS) { _pam_log_debug(ctx, LOG_ALERT, "password - " "new password not obtained"); pass_old = NULL;/* tidy up */ goto out; } /* * At this point we know who the user is and what they * propose as their new password. Verify that the new * password is acceptable. */ if (pass_new[0] == '\0') {/* "\0" password = NULL */ pass_new = NULL; } } /* * By reaching here we have approved the passwords and must now * rebuild the password database file. */ pam_get_data(pamh, PAM_WINBIND_PWD_LAST_SET, (const void **) &pwdlastset_update); /* * if cached creds were enabled, make sure to set the * WINBIND_CACHED_LOGIN bit here in order to have winbindd * update the cached creds storage - gd */ if (cached_login) { ctx->ctrl |= WINBIND_CACHED_LOGIN; } ret = winbind_chauthtok_request(ctx, user, pass_old, pass_new, pwdlastset_update); if (ret != PAM_SUCCESS) { pass_old = pass_new = NULL; goto out; } if (_pam_require_krb5_auth_after_chauthtok(ctx, user)) { const char *member = NULL; const char *cctype = NULL; int warn_pwd_expire; struct wbcLogonUserInfo *info = NULL; member = get_member_from_config(ctx); cctype = get_krb5_cc_type_from_config(ctx); warn_pwd_expire = get_warn_pwd_expire_from_config(ctx); /* Keep WINBIND_CACHED_LOGIN bit for * authentication after changing the password. * This will update the cached credentials in case * that winbindd_dual_pam_chauthtok() fails * to update them. * --- BoYang * */ ret = winbind_auth_request(ctx, user, pass_new, member, cctype, 0, &error, &info, NULL, &username_ret); pass_old = pass_new = NULL; if (ret == PAM_SUCCESS) { struct wbcAuthUserInfo *user_info = NULL; if (info && info->info) { user_info = info->info; } /* warn a user if the password is about to * expire soon */ _pam_warn_password_expiry(ctx, user_info, warn_pwd_expire, NULL, NULL); /* set some info3 info for other modules in the * stack */ _pam_set_data_info3(ctx, user_info); /* put krb5ccname into env */ _pam_setup_krb5_env(ctx, info); if (username_ret) { pam_set_item(pamh, PAM_USER, username_ret); _pam_log_debug(ctx, LOG_INFO, "Returned user was '%s'", username_ret); free(username_ret); } } if (info && info->blobs) { wbcFreeMemory(info->blobs); } wbcFreeMemory(info); goto out; } } else { ret = PAM_SERVICE_ERR; } out: { /* Deal with offline errors. */ int i; const char *codes[] = { "NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND", "NT_STATUS_NO_LOGON_SERVERS", "NT_STATUS_ACCESS_DENIED" }; for (i=0; i 2000 * Copyright (c) Tim Potter 2000 * Copyright (c) Andrew Bartlettt 2002 * Copyright (c) Guenther Deschner 2005-2008 * Copyright (c) Jan Rêkorajski 1999. * Copyright (c) Andrew G. Morgan 1996-8. * Copyright (c) Alex O. Yuriev, 1996. * Copyright (c) Cristian Gafton 1996. * Copyright (C) Elliot Lee 1996, Red Hat Software. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */