Commit message: fail authentication for single group name which cannot be converted to sid

furthermore if more than one name is supplied and no sid is converted
then also fail.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=8598

Signed-off-by: Noel Power <noel.power@suse.com>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: David Disseldorp <ddiss@samba.org>

Autobuild-User(master): David Disseldorp <ddiss@samba.org>
Autobuild-Date(master): Fri Nov 29 15:45:11 CET 2013 on sn-devel-104
CVE: CVE-2012-6150
CWE: CWE-20
Vulnerability description: The winbind_name_list_to_sid_string_list function in nsswitch/pam_winbind.c in Samba through 4.1.2 handles invalid require_membership_of group names by accepting authentication by any user, which allows remote authenticated users to bypass intended access restrictions in opportunistic circumstances by leveraging an administrator's pam_winbind configuration-file mistake.

Project: samba
File: pam_winbind.c

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;
};

Project: samba
File: pam_winbind.c

static bool _pam_winbind_change_pwd(struct pwb_context *ctx)
{
	struct pam_message msg, *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;
}

Project: samba
File: pam_winbind.c

static void _pam_warn_password_expiry(struct pwb_context *ctx,
				      const struct wbcAuthUserInfo *info,
				      const struct wbcUserPasswordPolicyInfo *policy,
				      int warn_pwd_expire,
				      bool *already_expired,
				      bool *change_pwd)
{
	time_t now = time(NULL);
	time_t next_change = 0;

	if (!info || !policy) {
		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;
	}

	/* now check for the global password policy */
	/* good catch from Ralf Haferkamp: an expiry of "never" is translated
	 * to -1 */
	if ((policy->expire == (int64_t)-1) ||
	    (policy->expire == 0)) {
		return;
	}

	next_change = info->pass_last_set_time + policy->expire;

	if (_pam_send_password_expiry_message(ctx, next_change, now,
					      warn_pwd_expire,
					      already_expired,
					      change_pwd)) {
		return;
	}

	/* no warning sent */
}

Project: samba
File: pam_winbind.c

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 = wbcGetpwnam(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;
}

Project: samba
File: pam_winbind.c

static int converse(const pam_handle_t *pamh,
		    int nargs,
		    struct pam_message **message,
		    struct pam_response **response)
{
	int retval;
	struct pam_conv *conv;

	retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
	if (retval == PAM_SUCCESS) {
		retval = conv->conv(nargs,
				    (const struct pam_message **)message,
				    response, conv->appdata_ptr);
	}

	return retval; /* propagate error status */
}

Project: samba
File: pam_winbind.c

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, &ctx);
	if (ret) {
		goto out;
	}

	_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:
				/* fall through, since new token is required in this case */
			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;
}

Project: samba
File: pam_winbind.c

static int _pam_delete_cred(pam_handle_t *pamh, int flags,
			 int argc, 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, &ctx);
	if (retval) {
		goto out;
	}

	_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_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 = wbcLogoffUserEx(&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;
}

Project: samba
File: pam_winbind.c

static bool _pam_winbind_change_pwd(struct pwb_context *ctx)
{
	return false;
}

Project: samba
File: pam_winbind.c

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,
				struct wbcUserPasswordPolicyInfo **p_policy,
				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 |
			 WBFLAG_PAM_GET_PWD_POLICY;

	struct wbcLogonUserInfo *info = NULL;
	struct wbcAuthUserInfo *user_info = NULL;
	struct wbcAuthErrorInfo *error = NULL;
	struct wbcUserPasswordPolicyInfo *policy = 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 = wbcLogonUser(&logon, &info, &error, &policy);
	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_policy && policy) {
		*p_policy = policy;
	}

	if (p_error && error) {
		/* We want to process the error in the caller. */
		*p_error = error;
		return ret;
	}

	for (i=0; i<ARRAY_SIZE(codes); i++) {
		int _ret = ret;
		if (_pam_check_remark_auth_err(ctx, error, codes[i], &_ret)) {
			ret = _ret;
			goto done;
		}
	}

	if ((ret == PAM_SUCCESS) && user_info && policy && info) {

		bool already_expired = false;
		bool change_pwd = false;

		/* warn a user if the password is about to expire soon */
		_pam_warn_password_expiry(ctx, user_info, policy,
					  warn_pwd_expire,
					  &already_expired,
					  &change_pwd);

		if (already_expired == true) {

			SMB_TIME_T last_set = user_info->pass_last_set_time;

			_pam_log_debug(ctx, LOG_DEBUG,
				       "Password has expired "
				       "(Password was last set: %lld, "
				       "the policy says it should expire here "
				       "%lld (now it's: %ld))\n",
				       (long long int)last_set,
				       (long long int)last_set +
				       policy->expire,
				       (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);
	}
	if (error && !p_error) {
		wbcFreeMemory(error);
	}
	if (info && !p_info) {
		wbcFreeMemory(info);
	}
	if (policy && !p_policy) {
		wbcFreeMemory(policy);
	}

	return ret;
}

Project: samba
File: pam_winbind.c

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], *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;
}

Project: samba
File: pam_winbind.c

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;

	/* 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;
	}
	if ((p = strchr(name, '@')) != NULL) {
		*p = 0;
		domain = p + 1;
	}

	/* Convert the UPN to a SID */

	wbc_status = wbcLookupName(domain, name, &sid, &type);
	if (!WBC_ERROR_IS_OK(wbc_status)) {
		return NULL;
	}

	/* Convert the the SID back to the sAMAccountName */

	wbc_status = wbcLookupSid(&sid, &domain, &name, &type);
	if (!WBC_ERROR_IS_OK(wbc_status)) {
		return NULL;
	}

	return talloc_asprintf(ctx, "%s%c%s", domain, sep, name);
}

Project: samba
File: pam_winbind.c

static inline void textdomain_init(void)
{
	if (!initialized) {
		bindtextdomain(MODULE_NAME, LOCALEDIR);
		initialized = 1;
	}
	return;
}

Project: samba
File: pam_winbind.c

static int _pam_winbind_init_context(pam_handle_t *pamh,
				     int flags,
				     int argc,
				     const char **argv,
				     struct pwb_context **ctx_p)
{
	struct pwb_context *r = NULL;

#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;
	r->ctrl = _pam_parse(pamh, flags, argc, argv, &r->dict);
	if (r->ctrl == -1) {
		TALLOC_FREE(r);
		return PAM_SYSTEM_ERR;
	}

	*ctx_p = r;

	return PAM_SUCCESS;
}

Project: samba
File: pam_winbind.c

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; 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;
			}
			_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 = iniparser_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;
}

Project: samba
File: pam_winbind.c

static int _pam_parse(const pam_handle_t *pamh,
		      int flags,
		      int argc,
		      const char **argv,
		      dictionary **result_d)
{
	int ctrl = 0;
	const char *config_file = NULL;
	int i;
	const char **v;
	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 = iniparser_load(discard_const_p(char, config_file));
	if (d == NULL) {
		goto config_from_pam;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:debug"), false)) {
		ctrl |= WINBIND_DEBUG_ARG;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:debug_state"), false)) {
		ctrl |= WINBIND_DEBUG_STATE;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:cached_login"), false)) {
		ctrl |= WINBIND_CACHED_LOGIN;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:krb5_auth"), false)) {
		ctrl |= WINBIND_KRB5_AUTH;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:silent"), false)) {
		ctrl |= WINBIND_SILENT;
	}

	if (iniparser_getstring_nonempty(d, discard_const_p(char, "global:krb5_ccache_type"), NULL) != NULL) {
		ctrl |= WINBIND_KRB5_CCACHE_TYPE;
	}

	if ((iniparser_getstring_nonempty(d, discard_const_p(char, "global:require-membership-of"), NULL)
	     != NULL) ||
	    (iniparser_getstring_nonempty(d, discard_const_p(char, "global:require_membership_of"), NULL)
	     != NULL)) {
		ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "global:try_first_pass"), false)) {
		ctrl |= WINBIND_TRY_FIRST_PASS_ARG;
	}

	if (iniparser_getint(d, discard_const_p(char, "global:warn_pwd_expire"), 0)) {
		ctrl |= WINBIND_WARN_PWD_EXPIRE;
	}

	if (iniparser_getboolean(d, discard_const_p(char, "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, "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 (!strncasecmp(*v, "require_membership_of",
				      strlen("require_membership_of")))
			ctrl |= WINBIND_REQUIRED_MEMBERSHIP;
		else if (!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 {
			__pam_log(pamh, ctrl, LOG_ERR,
				 "pam_parse: unknown option: %s", *v);
			return -1;
		}

	}

	if (result_d) {
		*result_d = d;
	} else {
		if (d) {
			iniparser_freedict(d);
		}
	}

	return ctrl;
};

Project: samba
File: pam_winbind.c

static void _pam_log_int(const pam_handle_t *pamh,
			 int err,
			 const char *format,
			 va_list args)
{
	char *format2 = NULL;
	const char *service;

	pam_get_item(pamh, PAM_SERVICE, (const void **) &service);

	format2 = (char *)malloc(strlen(MODULE_NAME)+strlen(format)+strlen(service)+5);
	if (format2 == NULL) {
		/* what else todo ? */
		vsyslog(err, format, args);
		return;
	}

	sprintf(format2, "%s(%s): %s", MODULE_NAME, service, format);
	vsyslog(err, format2, args);
	SAFE_FREE(format2);
}

Project: samba
File: pam_winbind.c

int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
		     int argc, const char **argv)
{
	unsigned int lctrl;
	int ret;
	bool cached_login = false;

	/* <DO NOT free() THESE> */
	const char *user;
	const char *pass_old;
	const char *pass_new;
	/* </DO NOT free() THESE> */

	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, &ctx);
	if (ret) {
		goto out;
	}

	_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_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, 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;
		}
		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) {
			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;
			struct wbcUserPasswordPolicyInfo *policy = 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, &policy,
						   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, policy,
							  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);
			wbcFreeMemory(policy);

			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<ARRAY_SIZE(codes); i++) {
			int _ret;
			if (_pam_check_remark_auth_err(ctx, error, codes[i], &_ret)) {
				break;
			}
		}
	}

	wbcFreeMemory(error);

	_PAM_LOG_FUNCTION_LEAVE("pam_sm_chauthtok", ctx, ret);

	TALLOC_FREE(ctx);

	return ret;
}

Project: samba
File: pam_winbind.c

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;
}

Project: samba
File: pam_winbind.c

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 = wbcLookupName("", 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;
}

Project: samba
File: pam_winbind.c

static char *iniparser_getstring_nonempty(dictionary *d, char *key, char *def)
{
	char *ret = iniparser_getstring(d, key, def);
	if (ret && strlen(ret) == 0) {
		ret = NULL;
	}
	return ret;
}

Project: samba
File: pam_winbind.c

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_log(ctx, LOG_ERR,
			 "failed to set KRB5CCNAME to %s: %s",
			 var, pam_strerror(ctx->pamh, ret));
	}
	free(var);
}

Project: samba
File: pam_winbind.c

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, &ctx);
	if (retval) {
		goto out;
	}

	_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,
				      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);
	}

	if (ctx != NULL) {
		_PAM_LOG_FUNCTION_LEAVE("pam_sm_authenticate", ctx, retval);
		TALLOC_FREE(ctx);
	}

	return retval;
}

Project: samba
File: pam_winbind.c

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
	 * */

	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;
}

Project: samba
File: pam_winbind.c

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 = wbcChangeUserPasswordEx(&params, &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; i<ARRAY_SIZE(codes); i++) {
		int _ret = ret;
		if (_pam_check_remark_auth_err(ctx, error, codes[i], &_ret)) {
			ret = _ret;
			goto done;
		}
	}

	if (!strcasecmp(error->nt_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 (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;
}

Project: samba
File: pam_winbind.c

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 malformated 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;
}

Project: samba
File: pam_winbind.c

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);
}