+{
+ NTSTATUS status;
+ char new_pass[512];
+ uint32 new_pass_len;
+ void *sam_ctx = NULL;
+ const char *user_dn, *domain_dn = NULL;
+ int ret;
+ struct ldb_message **res, mod;
+ const char * const attrs[] = { "objectSid", "ntPwdHash", NULL };
+ const char * const dom_attrs[] = { "minPwdLength", "pwdHistoryLength",
+ "pwdProperties", "minPwdAge", "maxPwdAge",
+ NULL };
+ const char *domain_sid;
+ struct samr_Hash *ntPwdHash;
+ struct samr_DomInfo1 *dominfo;
+ struct samr_ChangeReject *reject;
+ uint32 reason = 0;
+
+ ZERO_STRUCT(r->out);
+
+ if (r->in.nt_password == NULL ||
+ r->in.nt_verifier == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto failed;
+ }
+
+ /* this call doesn't take a policy handle, so we need to open
+ the sam db from scratch */
+ sam_ctx = samdb_connect();
+ if (sam_ctx == NULL) {
+ status = NT_STATUS_INVALID_SYSTEM_SERVICE;
+ goto failed;
+ }
+
+ /* we need the users dn and the domain dn (derived from the
+ user SID). We also need the current lm and nt password hashes
+ in order to decrypt the incoming passwords */
+ ret = samdb_search(sam_ctx,
+ mem_ctx, NULL, &res, attrs,
+ "(&(sAMAccountName=%s)(objectclass=user))",
+ r->in.account->name);
+ if (ret != 1) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto failed;
+ }
+
+ user_dn = res[0]->dn;
+
+ ret = samdb_result_hashes(mem_ctx, res[0], "ntPwdHash", &ntPwdHash);
+ if (ret != 1) {
+ status = NT_STATUS_WRONG_PASSWORD;
+ goto failed;
+ }
+
+ /* decrypt the password we have been given */
+ SamOEMhash(r->in.nt_password->data, ntPwdHash->hash, 516);
+
+ if (!decode_pw_buffer(r->in.nt_password->data, new_pass, sizeof(new_pass),
+ &new_pass_len, STR_UNICODE)) {
+ DEBUG(3,("samr: failed to decode password buffer\n"));
+ status = NT_STATUS_WRONG_PASSWORD;
+ goto failed;
+ }
+
+ /* work out the domain dn */
+ domain_sid = samdb_result_sid_prefix(mem_ctx, res[0], "objectSid");
+ if (domain_sid == NULL) {
+ status = NT_STATUS_NO_SUCH_DOMAIN;
+ goto failed;
+ }
+
+ domain_dn = samdb_search_string(sam_ctx, mem_ctx, NULL, "dn",
+ "(objectSid=%s)", domain_sid);
+ if (!domain_dn) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto failed;
+ }
+
+
+ ZERO_STRUCT(mod);
+ mod.dn = talloc_strdup(mem_ctx, user_dn);
+ if (!mod.dn) {
+ status = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ /* set the password - samdb needs to know both the domain and user DNs,
+ so the domain password policy can be used */
+ status = samdb_set_password(sam_ctx, mem_ctx,
+ user_dn, domain_dn,
+ &mod, new_pass,
+ NULL, NULL,
+ True, &reason);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ /* modify the samdb record */
+ ret = samdb_replace(sam_ctx, mem_ctx, &mod);
+ if (ret != 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto failed;
+ }
+
+ samdb_close(sam_ctx);
+ return NT_STATUS_OK;
+
+failed:
+ if (sam_ctx) {
+ samdb_close(sam_ctx);
+ }
+
+ /* on failure we need to fill in the reject reasons */
+ dominfo = talloc_p(mem_ctx, struct samr_DomInfo1);
+ if (dominfo == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ reject = talloc_p(mem_ctx, struct samr_ChangeReject);
+ if (reject == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(dominfo);
+ ZERO_STRUCTP(reject);
+
+ reject->reason = reason;
+
+ r->out.dominfo = dominfo;
+ r->out.reject = reject;
+
+ if (!domain_dn) {
+ return status;
+ }
+
+ ret = samdb_search(sam_ctx,
+ mem_ctx, NULL, &res, dom_attrs,
+ "dn=%s", domain_dn);
+ if (ret != 1) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto failed;
+ }
+
+ dominfo->min_pwd_len = samdb_result_uint(res[0], "minPwdLength", 0);
+ dominfo->password_properties = samdb_result_uint(res[0], "pwdProperties", 0);
+ dominfo->password_history = samdb_result_uint(res[0], "pwdHistoryLength", 0);
+ dominfo->max_password_age = samdb_result_int64(res[0], "maxPwdAge", 0);
+ dominfo->min_password_age = samdb_result_int64(res[0], "minPwdAge", 0);
+
+ return status;