2 Unix SMB/CIFS implementation.
4 kpasswd Server implementation
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
7 Copyright (C) Andrew Tridgell 2005
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 #include "smbd/service_task.h"
25 #include "auth/gensec/gensec.h"
26 #include "auth/credentials/credentials.h"
27 #include "auth/auth.h"
28 #include "dsdb/samdb/samdb.h"
29 #include "../lib/util/util_ldb.h"
30 #include "libcli/security/security.h"
31 #include "param/param.h"
32 #include "kdc/kdc-server.h"
33 #include "kdc/kdc-glue.h"
34 #include "dsdb/common/util.h"
35 #include "kdc/kpasswd_glue.h"
37 /* Return true if there is a valid error packet formed in the error_blob */
38 static bool kpasswdd_make_error_reply(struct kdc_server *kdc,
41 const char *error_string,
42 DATA_BLOB *error_blob)
44 char *error_string_utf8;
47 DEBUG(result_code ? 3 : 10, ("kpasswdd: %s\n", error_string));
49 if (!push_utf8_talloc(mem_ctx, &error_string_utf8, error_string, &len)) {
53 *error_blob = data_blob_talloc(mem_ctx, NULL, 2 + len + 1);
54 if (!error_blob->data) {
57 RSSVAL(error_blob->data, 0, result_code);
58 memcpy(error_blob->data + 2, error_string_utf8, len + 1);
62 /* Return true if there is a valid error packet formed in the error_blob */
63 static bool kpasswdd_make_unauth_error_reply(struct kdc_server *kdc,
66 const char *error_string,
67 DATA_BLOB *error_blob)
71 DATA_BLOB error_bytes;
72 krb5_data k5_error_bytes, k5_error_blob;
73 ret = kpasswdd_make_error_reply(kdc, mem_ctx, result_code, error_string,
78 k5_error_bytes.data = error_bytes.data;
79 k5_error_bytes.length = error_bytes.length;
80 kret = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
88 *error_blob = data_blob_talloc(mem_ctx, k5_error_blob.data, k5_error_blob.length);
89 smb_krb5_free_data_contents(kdc->smb_krb5_context->krb5_context,
91 if (!error_blob->data) {
97 static bool kpasswd_make_pwchange_reply(struct kdc_server *kdc,
100 enum samPwdChangeReason reject_reason,
101 struct samr_DomInfo1 *dominfo,
102 DATA_BLOB *error_blob)
104 if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
105 return kpasswdd_make_error_reply(kdc, mem_ctx,
106 KRB5_KPASSWD_ACCESSDENIED,
107 "No such user when changing password",
110 if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
111 return kpasswdd_make_error_reply(kdc, mem_ctx,
112 KRB5_KPASSWD_ACCESSDENIED,
113 "Not permitted to change password",
116 if (dominfo && NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
117 const char *reject_string;
118 switch (reject_reason) {
119 case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
120 reject_string = talloc_asprintf(mem_ctx, "Password too short, password must be at least %d characters long.",
121 dominfo->min_password_length);
123 case SAM_PWD_CHANGE_NOT_COMPLEX:
124 reject_string = "Password does not meet complexity requirements";
126 case SAM_PWD_CHANGE_PWD_IN_HISTORY:
127 reject_string = talloc_asprintf(mem_ctx, "Password is already in password history. New password must not match any of your %d previous passwords.",
128 dominfo->password_history_length);
131 reject_string = "Password change rejected, password changes may not be permitted on this account, or the minimum password age may not have elapsed.";
134 return kpasswdd_make_error_reply(kdc, mem_ctx,
135 KRB5_KPASSWD_SOFTERROR,
139 if (!NT_STATUS_IS_OK(status)) {
140 return kpasswdd_make_error_reply(kdc, mem_ctx,
141 KRB5_KPASSWD_HARDERROR,
142 talloc_asprintf(mem_ctx, "failed to set password: %s", nt_errstr(status)),
146 return kpasswdd_make_error_reply(kdc, mem_ctx, KRB5_KPASSWD_SUCCESS,
152 A user password change
154 Return true if there is a valid error packet (or success) formed in
157 static bool kpasswdd_change_password(struct kdc_server *kdc,
159 struct auth_session_info *session_info,
160 const DATA_BLOB *password,
164 NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
165 enum samPwdChangeReason reject_reason;
166 struct samr_DomInfo1 *dominfo;
167 const char *error_string;
169 status = samdb_kpasswd_change_password(mem_ctx,
171 kdc->task->event_ctx,
179 if (!NT_STATUS_IS_OK(status)) {
180 return kpasswdd_make_error_reply(kdc,
182 KRB5_KPASSWD_ACCESSDENIED,
187 return kpasswd_make_pwchange_reply(kdc,
195 static bool kpasswd_process_request(struct kdc_server *kdc,
197 struct gensec_security *gensec_security,
202 struct auth_session_info *session_info;
205 if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security,
208 return kpasswdd_make_error_reply(kdc, mem_ctx,
209 KRB5_KPASSWD_HARDERROR,
210 "gensec_session_info failed!",
215 case KRB5_KPASSWD_VERS_CHANGEPW:
218 if (!convert_string_talloc_handle(mem_ctx, lpcfg_iconv_handle(kdc->task->lp_ctx),
220 (const char *)input->data,
222 (void **)&password.data, &pw_len)) {
225 password.length = pw_len;
227 return kpasswdd_change_password(kdc, mem_ctx, session_info,
230 case KRB5_KPASSWD_VERS_SETPW:
233 enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
234 struct samr_DomInfo1 *dominfo = NULL;
235 struct ldb_context *samdb;
236 krb5_context context = kdc->smb_krb5_context->krb5_context;
238 ChangePasswdDataMS chpw;
241 krb5_principal principal;
242 char *set_password_on_princ;
243 struct ldb_dn *set_password_on_dn;
244 bool service_principal_name = false;
249 ret = decode_ChangePasswdDataMS(input->data, input->length,
252 return kpasswdd_make_error_reply(kdc, mem_ctx,
253 KRB5_KPASSWD_MALFORMED,
254 "failed to decode password change structure",
258 if (!convert_string_talloc_handle(mem_ctx, lpcfg_iconv_handle(kdc->task->lp_ctx),
260 (const char *)chpw.newpasswd.data,
261 chpw.newpasswd.length,
262 (void **)&password.data, &pw_len)) {
263 free_ChangePasswdDataMS(&chpw);
267 password.length = pw_len;
269 if ((chpw.targname && !chpw.targrealm)
270 || (!chpw.targname && chpw.targrealm)) {
271 free_ChangePasswdDataMS(&chpw);
272 return kpasswdd_make_error_reply(kdc, mem_ctx,
273 KRB5_KPASSWD_MALFORMED,
274 "Realm and principal must be both present, or neither present",
277 if (chpw.targname && chpw.targrealm) {
278 ret = krb5_build_principal_ext(kdc->smb_krb5_context->krb5_context,
280 strlen(*chpw.targrealm),
283 free_ChangePasswdDataMS(&chpw);
284 return kpasswdd_make_error_reply(kdc, mem_ctx,
285 KRB5_KPASSWD_MALFORMED,
286 "failed to get principal",
289 if (copy_PrincipalName(chpw.targname, &principal->name)) {
290 free_ChangePasswdDataMS(&chpw);
291 krb5_free_principal(context, principal);
292 return kpasswdd_make_error_reply(kdc, mem_ctx,
293 KRB5_KPASSWD_MALFORMED,
294 "failed to extract principal to set",
298 free_ChangePasswdDataMS(&chpw);
299 return kpasswdd_change_password(kdc, mem_ctx, session_info,
302 free_ChangePasswdDataMS(&chpw);
304 if (principal->name.name_string.len >= 2) {
305 service_principal_name = true;
307 /* We use this, rather than 'no realm' flag,
308 * as we don't want to accept a password
309 * change on a principal from another realm */
311 if (krb5_unparse_name_short(context, principal, &set_password_on_princ) != 0) {
312 krb5_free_principal(context, principal);
313 return kpasswdd_make_error_reply(kdc, mem_ctx,
314 KRB5_KPASSWD_MALFORMED,
315 "krb5_unparse_name failed!",
319 if (krb5_unparse_name(context, principal, &set_password_on_princ) != 0) {
320 krb5_free_principal(context, principal);
321 return kpasswdd_make_error_reply(kdc, mem_ctx,
322 KRB5_KPASSWD_MALFORMED,
323 "krb5_unparse_name failed!",
327 krb5_free_principal(context, principal);
329 samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx, session_info, 0);
331 free(set_password_on_princ);
332 return kpasswdd_make_error_reply(kdc, mem_ctx,
333 KRB5_KPASSWD_HARDERROR,
334 "Unable to open database!",
338 DEBUG(3, ("%s\\%s (%s) is changing password of %s\n",
339 session_info->info->domain_name,
340 session_info->info->account_name,
341 dom_sid_string(mem_ctx, &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]),
342 set_password_on_princ));
343 ret = ldb_transaction_start(samdb);
344 if (ret != LDB_SUCCESS) {
345 free(set_password_on_princ);
346 status = NT_STATUS_TRANSACTION_ABORTED;
347 return kpasswd_make_pwchange_reply(kdc, mem_ctx,
349 SAM_PWD_CHANGE_NO_ERROR,
354 if (service_principal_name) {
355 status = crack_service_principal_name(samdb, mem_ctx,
356 set_password_on_princ,
357 &set_password_on_dn, NULL);
359 status = crack_user_principal_name(samdb, mem_ctx,
360 set_password_on_princ,
361 &set_password_on_dn, NULL);
363 free(set_password_on_princ);
364 if (!NT_STATUS_IS_OK(status)) {
365 ldb_transaction_cancel(samdb);
366 return kpasswd_make_pwchange_reply(kdc, mem_ctx,
368 SAM_PWD_CHANGE_NO_ERROR,
373 if (NT_STATUS_IS_OK(status)) {
374 /* Admin password set */
375 status = samdb_set_password(samdb, mem_ctx,
376 set_password_on_dn, NULL,
377 &password, NULL, NULL,
378 NULL, NULL, /* this is not a user password change */
379 &reject_reason, &dominfo);
382 if (NT_STATUS_IS_OK(status)) {
383 ret = ldb_transaction_commit(samdb);
384 if (ret != LDB_SUCCESS) {
385 DEBUG(1,("Failed to commit transaction to set password on %s: %s\n",
386 ldb_dn_get_linearized(set_password_on_dn),
387 ldb_errstring(samdb)));
388 status = NT_STATUS_TRANSACTION_ABORTED;
391 ldb_transaction_cancel(samdb);
393 return kpasswd_make_pwchange_reply(kdc, mem_ctx,
400 return kpasswdd_make_error_reply(kdc, mem_ctx,
401 KRB5_KPASSWD_BAD_VERSION,
402 talloc_asprintf(mem_ctx,
403 "Protocol version %u not supported",
409 kdc_code kpasswdd_process(struct kdc_server *kdc,
413 struct tsocket_address *peer_addr,
414 struct tsocket_address *my_addr,
418 const uint16_t header_len = 6;
421 uint16_t krb_priv_len;
424 DATA_BLOB ap_req, krb_priv_req;
425 DATA_BLOB krb_priv_rep = data_blob(NULL, 0);
426 DATA_BLOB ap_rep = data_blob(NULL, 0);
427 DATA_BLOB kpasswd_req, kpasswd_rep;
428 struct cli_credentials *server_credentials;
429 struct gensec_security *gensec_security;
430 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
439 talloc_free(tmp_ctx);
440 return KDC_PROXY_REQUEST;
443 /* Be parinoid. We need to ensure we don't just let the
444 * caller lead us into a buffer overflow */
445 if (input->length <= header_len) {
446 talloc_free(tmp_ctx);
450 len = RSVAL(input->data, 0);
451 if (input->length != len) {
452 talloc_free(tmp_ctx);
456 /* There are two different versions of this protocol so far,
457 * plus others in the standards pipe. Fortunetly they all
458 * take a very similar framing */
459 version = RSVAL(input->data, 2);
460 ap_req_len = RSVAL(input->data, 4);
461 if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
462 talloc_free(tmp_ctx);
466 krb_priv_len = len - ap_req_len;
467 ap_req = data_blob_const(&input->data[header_len], ap_req_len);
468 krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);
470 server_credentials = cli_credentials_init(tmp_ctx);
471 if (!server_credentials) {
472 DEBUG(1, ("Failed to init server credentials\n"));
473 talloc_free(tmp_ctx);
477 /* We want the credentials subsystem to use the krb5 context
478 * we already have, rather than a new context */
479 cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
480 cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
482 keytab_name = talloc_asprintf(server_credentials, "HDB:samba4&%p", kdc->base_ctx);
484 cli_credentials_set_username(server_credentials, "kadmin/changepw", CRED_SPECIFIED);
485 ret = cli_credentials_set_keytab_name(server_credentials, kdc->task->lp_ctx, keytab_name, CRED_SPECIFIED);
487 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
488 KRB5_KPASSWD_HARDERROR,
489 talloc_asprintf(mem_ctx,
490 "Failed to obtain server credentials for kadmin/changepw!"),
496 talloc_free(tmp_ctx);
500 /* We don't strictly need to call this wrapper, and could call
501 * gensec_server_start directly, as we have no need for NTLM
502 * and we have a PAC, but this ensures that the wrapper can be
503 * safely extended for other helpful things in future */
504 nt_status = samba_server_gensec_start(tmp_ctx, kdc->task->event_ctx,
510 if (!NT_STATUS_IS_OK(nt_status)) {
511 talloc_free(tmp_ctx);
515 /* The kerberos PRIV packets include these addresses. MIT
516 * clients check that they are present */
518 /* Skip this part for now, it breaks with a NetAPP filer and
519 * in any case where the client address is behind NAT. If
520 * older MIT clients need this, we might have to insert more
523 nt_status = gensec_set_remote_address(gensec_security, peer_addr);
524 if (!NT_STATUS_IS_OK(nt_status)) {
525 talloc_free(tmp_ctx);
530 nt_status = gensec_set_local_address(gensec_security, my_addr);
531 if (!NT_STATUS_IS_OK(nt_status)) {
532 talloc_free(tmp_ctx);
536 /* We want the GENSEC wrap calls to generate PRIV tokens */
537 gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
539 nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
540 if (!NT_STATUS_IS_OK(nt_status)) {
541 talloc_free(tmp_ctx);
545 /* Accept the AP-REQ and generate teh AP-REP we need for the reply */
546 nt_status = gensec_update_ev(gensec_security, tmp_ctx, kdc->task->event_ctx, ap_req, &ap_rep);
547 if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
549 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
550 KRB5_KPASSWD_HARDERROR,
551 talloc_asprintf(mem_ctx,
552 "gensec_update failed: %s",
553 nt_errstr(nt_status)),
559 talloc_free(tmp_ctx);
563 /* Extract the data from the KRB-PRIV half of the message */
564 nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
565 if (!NT_STATUS_IS_OK(nt_status)) {
566 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
567 KRB5_KPASSWD_HARDERROR,
568 talloc_asprintf(mem_ctx,
569 "gensec_unwrap failed: %s",
570 nt_errstr(nt_status)),
576 talloc_free(tmp_ctx);
580 /* Figure out something to do with it (probably changing a password...) */
581 ret = kpasswd_process_request(kdc, tmp_ctx,
584 &kpasswd_req, &kpasswd_rep);
587 talloc_free(tmp_ctx);
591 /* And wrap up the reply: This ensures that the error message
592 * or success can be verified by the client */
593 nt_status = gensec_wrap(gensec_security, tmp_ctx,
594 &kpasswd_rep, &krb_priv_rep);
595 if (!NT_STATUS_IS_OK(nt_status)) {
596 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
597 KRB5_KPASSWD_HARDERROR,
598 talloc_asprintf(mem_ctx,
599 "gensec_wrap failed: %s",
600 nt_errstr(nt_status)),
606 talloc_free(tmp_ctx);
611 *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
613 talloc_free(tmp_ctx);
617 RSSVAL(reply->data, 0, reply->length);
618 RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
619 RSSVAL(reply->data, 4, ap_rep.length);
620 memcpy(reply->data + header_len,
623 memcpy(reply->data + header_len + ap_rep.length,
625 krb_priv_rep.length);
627 talloc_free(tmp_ctx);