8ac92cd478310296bcc47a20a15fe446e0c6dc43
[nivanova/samba-autobuild/.git] / source4 / kdc / kpasswdd.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    kpasswd Server implementation
5
6    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
7    Copyright (C) Andrew Tridgell        2005
8
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.
13
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.
18
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/>.
21 */
22
23 #include "includes.h"
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-glue.h"
33
34 /* Return true if there is a valid error packet formed in the error_blob */
35 static bool kpasswdd_make_error_reply(struct kdc_server *kdc,
36                                      TALLOC_CTX *mem_ctx,
37                                      uint16_t result_code,
38                                      const char *error_string,
39                                      DATA_BLOB *error_blob)
40 {
41         char *error_string_utf8;
42         size_t len;
43
44         DEBUG(result_code ? 3 : 10, ("kpasswdd: %s\n", error_string));
45
46         if (!push_utf8_talloc(mem_ctx, &error_string_utf8, error_string, &len)) {
47                 return false;
48         }
49
50         *error_blob = data_blob_talloc(mem_ctx, NULL, 2 + len + 1);
51         if (!error_blob->data) {
52                 return false;
53         }
54         RSSVAL(error_blob->data, 0, result_code);
55         memcpy(error_blob->data + 2, error_string_utf8, len + 1);
56         return true;
57 }
58
59 /* Return true if there is a valid error packet formed in the error_blob */
60 static bool kpasswdd_make_unauth_error_reply(struct kdc_server *kdc,
61                                             TALLOC_CTX *mem_ctx,
62                                             uint16_t result_code,
63                                             const char *error_string,
64                                             DATA_BLOB *error_blob)
65 {
66         bool ret;
67         int kret;
68         DATA_BLOB error_bytes;
69         krb5_data k5_error_bytes, k5_error_blob;
70         ret = kpasswdd_make_error_reply(kdc, mem_ctx, result_code, error_string,
71                                        &error_bytes);
72         if (!ret) {
73                 return false;
74         }
75         k5_error_bytes.data = error_bytes.data;
76         k5_error_bytes.length = error_bytes.length;
77         kret = krb5_mk_error(kdc->smb_krb5_context->krb5_context,
78                              result_code, NULL, &k5_error_bytes,
79                              NULL, NULL, NULL, NULL, &k5_error_blob);
80         if (kret) {
81                 return false;
82         }
83         *error_blob = data_blob_talloc(mem_ctx, k5_error_blob.data, k5_error_blob.length);
84         krb5_data_free(&k5_error_blob);
85         if (!error_blob->data) {
86                 return false;
87         }
88         return true;
89 }
90
91 static bool kpasswd_make_pwchange_reply(struct kdc_server *kdc,
92                                         TALLOC_CTX *mem_ctx,
93                                         NTSTATUS status,
94                                         enum samPwdChangeReason reject_reason,
95                                         struct samr_DomInfo1 *dominfo,
96                                         DATA_BLOB *error_blob)
97 {
98         if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
99                 return kpasswdd_make_error_reply(kdc, mem_ctx,
100                                                 KRB5_KPASSWD_ACCESSDENIED,
101                                                 "No such user when changing password",
102                                                 error_blob);
103         }
104         if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
105                 return kpasswdd_make_error_reply(kdc, mem_ctx,
106                                                 KRB5_KPASSWD_ACCESSDENIED,
107                                                 "Not permitted to change password",
108                                                 error_blob);
109         }
110         if (dominfo && NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
111                 const char *reject_string;
112                 switch (reject_reason) {
113                 case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
114                         reject_string = talloc_asprintf(mem_ctx, "Password too short, password must be at least %d characters long",
115                                                         dominfo->min_password_length);
116                         break;
117                 case SAM_PWD_CHANGE_NOT_COMPLEX:
118                         reject_string = "Password does not meet complexity requirements";
119                         break;
120                 case SAM_PWD_CHANGE_PWD_IN_HISTORY:
121                         reject_string = "Password is already in password history";
122                         break;
123                 default:
124                         reject_string = talloc_asprintf(mem_ctx, "Password must be at least %d characters long, and cannot match any of your %d previous passwords",
125                                                         dominfo->min_password_length, dominfo->password_history_length);
126                         break;
127                 }
128                 return kpasswdd_make_error_reply(kdc, mem_ctx,
129                                                 KRB5_KPASSWD_SOFTERROR,
130                                                 reject_string,
131                                                 error_blob);
132         }
133         if (!NT_STATUS_IS_OK(status)) {
134                 return kpasswdd_make_error_reply(kdc, mem_ctx,
135                                                  KRB5_KPASSWD_HARDERROR,
136                                                  talloc_asprintf(mem_ctx, "failed to set password: %s", nt_errstr(status)),
137                                                  error_blob);
138
139         }
140         return kpasswdd_make_error_reply(kdc, mem_ctx, KRB5_KPASSWD_SUCCESS,
141                                         "Password changed",
142                                         error_blob);
143 }
144
145 /*
146    A user password change
147
148    Return true if there is a valid error packet (or success) formed in
149    the error_blob
150 */
151 static bool kpasswdd_change_password(struct kdc_server *kdc,
152                                      TALLOC_CTX *mem_ctx,
153                                      struct auth_session_info *session_info,
154                                      const DATA_BLOB *password,
155                                      DATA_BLOB *reply)
156 {
157         NTSTATUS status;
158         enum samPwdChangeReason reject_reason;
159         struct samr_DomInfo1 *dominfo;
160         struct samr_Password *oldLmHash, *oldNtHash;
161         struct ldb_context *samdb;
162         const char * const attrs[] = { "dBCSPwd", "unicodePwd", NULL };
163         struct ldb_message **res;
164         int ret;
165
166         /* Fetch the old hashes to get the old password in order to perform
167          * the password change operation. Naturally it would be much better to
168          * have a password hash from an authentication around but this doesn't
169          * seem to be the case here. */
170         ret = gendb_search(kdc->samdb, mem_ctx, NULL, &res, attrs,
171                            "(&(objectClass=user)(sAMAccountName=%s))",
172                            session_info->info->account_name);
173         if (ret != 1) {
174                 return kpasswdd_make_error_reply(kdc, mem_ctx,
175                                                 KRB5_KPASSWD_ACCESSDENIED,
176                                                 "No such user when changing password",
177                                                 reply);
178         }
179
180         status = samdb_result_passwords(mem_ctx, kdc->task->lp_ctx, res[0],
181                                         &oldLmHash, &oldNtHash);
182         if (!NT_STATUS_IS_OK(status)) {
183                 return kpasswdd_make_error_reply(kdc, mem_ctx,
184                                                 KRB5_KPASSWD_ACCESSDENIED,
185                                                 "Not permitted to change password",
186                                                 reply);
187         }
188
189         /* Start a SAM with user privileges for the password change */
190         samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx,
191                               session_info, 0);
192         if (!samdb) {
193                 return kpasswdd_make_error_reply(kdc, mem_ctx,
194                                                 KRB5_KPASSWD_HARDERROR,
195                                                 "Failed to open samdb",
196                                                 reply);
197         }
198
199         DEBUG(3, ("Changing password of %s\\%s (%s)\n",
200                   session_info->info->domain_name,
201                   session_info->info->account_name,
202                   dom_sid_string(mem_ctx, &session_info->security_token->sids[PRIMARY_USER_SID_INDEX])));
203
204         /* Performs the password change */
205         status = samdb_set_password_sid(samdb, mem_ctx,
206                                         &session_info->security_token->sids[PRIMARY_USER_SID_INDEX],
207                                         password, NULL, NULL,
208                                         oldLmHash, oldNtHash, /* this is a user password change */
209                                         &reject_reason,
210                                         &dominfo);
211         return kpasswd_make_pwchange_reply(kdc, mem_ctx,
212                                            status,
213                                            reject_reason,
214                                            dominfo,
215                                            reply);
216
217 }
218
219 static bool kpasswd_process_request(struct kdc_server *kdc,
220                                     TALLOC_CTX *mem_ctx,
221                                     struct gensec_security *gensec_security,
222                                     uint16_t version,
223                                     DATA_BLOB *input,
224                                     DATA_BLOB *reply)
225 {
226         struct auth_session_info *session_info;
227         size_t pw_len;
228
229         if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security,
230                                                  &session_info))) {
231                 return kpasswdd_make_error_reply(kdc, mem_ctx,
232                                                 KRB5_KPASSWD_HARDERROR,
233                                                 "gensec_session_info failed!",
234                                                 reply);
235         }
236
237         switch (version) {
238         case KRB5_KPASSWD_VERS_CHANGEPW:
239         {
240                 DATA_BLOB password;
241                 if (!convert_string_talloc_convenience(mem_ctx, lpcfg_iconv_convenience(kdc->task->lp_ctx),
242                                                CH_UTF8, CH_UTF16,
243                                                (const char *)input->data,
244                                                input->length,
245                                                (void **)&password.data, &pw_len)) {
246                         return false;
247                 }
248                 password.length = pw_len;
249
250                 return kpasswdd_change_password(kdc, mem_ctx, session_info,
251                                                 &password, reply);
252         }
253         case KRB5_KPASSWD_VERS_SETPW:
254         {
255                 NTSTATUS status;
256                 enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
257                 struct samr_DomInfo1 *dominfo = NULL;
258                 struct ldb_context *samdb;
259                 krb5_context context = kdc->smb_krb5_context->krb5_context;
260
261                 ChangePasswdDataMS chpw;
262                 DATA_BLOB password;
263
264                 krb5_principal principal;
265                 char *set_password_on_princ;
266                 struct ldb_dn *set_password_on_dn;
267                 bool service_principal_name = false;
268
269                 size_t len;
270                 int ret;
271
272                 ret = decode_ChangePasswdDataMS(input->data, input->length,
273                                                 &chpw, &len);
274                 if (ret) {
275                         return kpasswdd_make_error_reply(kdc, mem_ctx,
276                                                         KRB5_KPASSWD_MALFORMED,
277                                                         "failed to decode password change structure",
278                                                         reply);
279                 }
280
281                 if (!convert_string_talloc_convenience(mem_ctx, lpcfg_iconv_convenience(kdc->task->lp_ctx),
282                                                CH_UTF8, CH_UTF16,
283                                                (const char *)chpw.newpasswd.data,
284                                                chpw.newpasswd.length,
285                                                (void **)&password.data, &pw_len)) {
286                         free_ChangePasswdDataMS(&chpw);
287                         return false;
288                 }
289
290                 password.length = pw_len;
291
292                 if ((chpw.targname && !chpw.targrealm)
293                     || (!chpw.targname && chpw.targrealm)) {
294                         free_ChangePasswdDataMS(&chpw);
295                         return kpasswdd_make_error_reply(kdc, mem_ctx,
296                                                         KRB5_KPASSWD_MALFORMED,
297                                                         "Realm and principal must be both present, or neither present",
298                                                         reply);
299                 }
300                 if (chpw.targname && chpw.targrealm) {
301                         ret = krb5_build_principal_ext(kdc->smb_krb5_context->krb5_context,
302                                                        &principal,
303                                                        strlen(*chpw.targrealm),
304                                                        *chpw.targrealm, 0);
305                         if (ret) {
306                                 free_ChangePasswdDataMS(&chpw);
307                                 return kpasswdd_make_error_reply(kdc, mem_ctx,
308                                                                 KRB5_KPASSWD_MALFORMED,
309                                                                 "failed to get principal",
310                                                                 reply);
311                         }
312                         if (copy_PrincipalName(chpw.targname, &principal->name)) {
313                                 free_ChangePasswdDataMS(&chpw);
314                                 krb5_free_principal(context, principal);
315                                 return kpasswdd_make_error_reply(kdc, mem_ctx,
316                                                                 KRB5_KPASSWD_MALFORMED,
317                                                                 "failed to extract principal to set",
318                                                                 reply);
319                         }
320                 } else {
321                         free_ChangePasswdDataMS(&chpw);
322                         return kpasswdd_change_password(kdc, mem_ctx, session_info,
323                                                         &password, reply);
324                 }
325                 free_ChangePasswdDataMS(&chpw);
326
327                 if (principal->name.name_string.len >= 2) {
328                         service_principal_name = true;
329
330                         /* We use this, rather than 'no realm' flag,
331                          * as we don't want to accept a password
332                          * change on a principal from another realm */
333
334                         if (krb5_unparse_name_short(context, principal, &set_password_on_princ) != 0) {
335                                 krb5_free_principal(context, principal);
336                                 return kpasswdd_make_error_reply(kdc, mem_ctx,
337                                                                  KRB5_KPASSWD_MALFORMED,
338                                                                  "krb5_unparse_name failed!",
339                                                                  reply);
340                         }
341                 } else {
342                         if (krb5_unparse_name(context, principal, &set_password_on_princ) != 0) {
343                                 krb5_free_principal(context, principal);
344                                 return kpasswdd_make_error_reply(kdc, mem_ctx,
345                                                                  KRB5_KPASSWD_MALFORMED,
346                                                                  "krb5_unparse_name failed!",
347                                                                  reply);
348                         }
349                 }
350                 krb5_free_principal(context, principal);
351
352                 samdb = samdb_connect(mem_ctx, kdc->task->event_ctx, kdc->task->lp_ctx, session_info, 0);
353                 if (!samdb) {
354                         free(set_password_on_princ);
355                         return kpasswdd_make_error_reply(kdc, mem_ctx,
356                                                          KRB5_KPASSWD_HARDERROR,
357                                                          "Unable to open database!",
358                                                          reply);
359                 }
360
361                 DEBUG(3, ("%s\\%s (%s) is changing password of %s\n",
362                           session_info->info->domain_name,
363                           session_info->info->account_name,
364                           dom_sid_string(mem_ctx, &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]),
365                           set_password_on_princ));
366                 ret = ldb_transaction_start(samdb);
367                 if (ret != LDB_SUCCESS) {
368                         free(set_password_on_princ);
369                         status = NT_STATUS_TRANSACTION_ABORTED;
370                         return kpasswd_make_pwchange_reply(kdc, mem_ctx,
371                                                            status,
372                                                            SAM_PWD_CHANGE_NO_ERROR,
373                                                            NULL,
374                                                            reply);
375                 }
376
377                 if (service_principal_name) {
378                         status = crack_service_principal_name(samdb, mem_ctx,
379                                                               set_password_on_princ,
380                                                               &set_password_on_dn, NULL);
381                 } else {
382                         status = crack_user_principal_name(samdb, mem_ctx,
383                                                            set_password_on_princ,
384                                                            &set_password_on_dn, NULL);
385                 }
386                 free(set_password_on_princ);
387                 if (!NT_STATUS_IS_OK(status)) {
388                         ldb_transaction_cancel(samdb);
389                         return kpasswd_make_pwchange_reply(kdc, mem_ctx,
390                                                            status,
391                                                            SAM_PWD_CHANGE_NO_ERROR,
392                                                            NULL,
393                                                            reply);
394                 }
395
396                 if (NT_STATUS_IS_OK(status)) {
397                         /* Admin password set */
398                         status = samdb_set_password(samdb, mem_ctx,
399                                                     set_password_on_dn, NULL,
400                                                     &password, NULL, NULL,
401                                                     NULL, NULL, /* this is not a user password change */
402                                                     &reject_reason, &dominfo);
403                 }
404
405                 if (NT_STATUS_IS_OK(status)) {
406                         ret = ldb_transaction_commit(samdb);
407                         if (ret != LDB_SUCCESS) {
408                                 DEBUG(1,("Failed to commit transaction to set password on %s: %s\n",
409                                          ldb_dn_get_linearized(set_password_on_dn),
410                                          ldb_errstring(samdb)));
411                                 status = NT_STATUS_TRANSACTION_ABORTED;
412                         }
413                 } else {
414                         ldb_transaction_cancel(samdb);
415                 }
416                 return kpasswd_make_pwchange_reply(kdc, mem_ctx,
417                                                    status,
418                                                    reject_reason,
419                                                    dominfo,
420                                                    reply);
421         }
422         default:
423                 return kpasswdd_make_error_reply(kdc, mem_ctx,
424                                                  KRB5_KPASSWD_BAD_VERSION,
425                                                  talloc_asprintf(mem_ctx,
426                                                                  "Protocol version %u not supported",
427                                                                  version),
428                                                  reply);
429         }
430 }
431
432 enum kdc_process_ret kpasswdd_process(struct kdc_server *kdc,
433                                       TALLOC_CTX *mem_ctx,
434                                       DATA_BLOB *input,
435                                       DATA_BLOB *reply,
436                                       struct tsocket_address *peer_addr,
437                                       struct tsocket_address *my_addr,
438                                       int datagram_reply)
439 {
440         bool ret;
441         const uint16_t header_len = 6;
442         uint16_t len;
443         uint16_t ap_req_len;
444         uint16_t krb_priv_len;
445         uint16_t version;
446         NTSTATUS nt_status;
447         DATA_BLOB ap_req, krb_priv_req;
448         DATA_BLOB krb_priv_rep = data_blob(NULL, 0);
449         DATA_BLOB ap_rep = data_blob(NULL, 0);
450         DATA_BLOB kpasswd_req, kpasswd_rep;
451         struct cli_credentials *server_credentials;
452         struct gensec_security *gensec_security;
453         TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
454
455         char *keytab_name;
456
457         if (!tmp_ctx) {
458                 return KDC_PROCESS_FAILED;
459         }
460
461         if (kdc->am_rodc) {
462                 talloc_free(tmp_ctx);
463                 return KDC_PROCESS_PROXY;
464         }
465
466         /* Be parinoid.  We need to ensure we don't just let the
467          * caller lead us into a buffer overflow */
468         if (input->length <= header_len) {
469                 talloc_free(tmp_ctx);
470                 return KDC_PROCESS_FAILED;
471         }
472
473         len = RSVAL(input->data, 0);
474         if (input->length != len) {
475                 talloc_free(tmp_ctx);
476                 return KDC_PROCESS_FAILED;
477         }
478
479         /* There are two different versions of this protocol so far,
480          * plus others in the standards pipe.  Fortunetly they all
481          * take a very similar framing */
482         version = RSVAL(input->data, 2);
483         ap_req_len = RSVAL(input->data, 4);
484         if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
485                 talloc_free(tmp_ctx);
486                 return KDC_PROCESS_FAILED;
487         }
488
489         krb_priv_len = len - ap_req_len;
490         ap_req = data_blob_const(&input->data[header_len], ap_req_len);
491         krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);
492
493         server_credentials = cli_credentials_init(tmp_ctx);
494         if (!server_credentials) {
495                 DEBUG(1, ("Failed to init server credentials\n"));
496                 talloc_free(tmp_ctx);
497                 return KDC_PROCESS_FAILED;
498         }
499
500         /* We want the credentials subsystem to use the krb5 context
501          * we already have, rather than a new context */
502         cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
503         cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
504
505         keytab_name = talloc_asprintf(server_credentials, "HDB:samba4&%p", kdc->base_ctx);
506
507         cli_credentials_set_username(server_credentials, "kadmin/changepw", CRED_SPECIFIED);
508         ret = cli_credentials_set_keytab_name(server_credentials, kdc->task->lp_ctx, keytab_name, CRED_SPECIFIED);
509         if (ret != 0) {
510                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
511                                                        KRB5_KPASSWD_HARDERROR,
512                                                        talloc_asprintf(mem_ctx,
513                                                                        "Failed to obtain server credentials for kadmin/changepw!"),
514                                                        &krb_priv_rep);
515                 ap_rep.length = 0;
516                 if (ret) {
517                         goto reply;
518                 }
519                 talloc_free(tmp_ctx);
520                 return ret;
521         }
522
523         /* We don't strictly need to call this wrapper, and could call
524          * gensec_server_start directly, as we have no need for NTLM
525          * and we have a PAC, but this ensures that the wrapper can be
526          * safely extended for other helpful things in future */
527         nt_status = samba_server_gensec_start(tmp_ctx, kdc->task->event_ctx,
528                                               kdc->task->msg_ctx,
529                                               kdc->task->lp_ctx,
530                                               server_credentials,
531                                               "kpasswd",
532                                               &gensec_security);
533         if (!NT_STATUS_IS_OK(nt_status)) {
534                 talloc_free(tmp_ctx);
535                 return KDC_PROCESS_FAILED;
536         }
537
538         /* The kerberos PRIV packets include these addresses.  MIT
539          * clients check that they are present */
540 #if 0
541         /* Skip this part for now, it breaks with a NetAPP filer and
542          * in any case where the client address is behind NAT.  If
543          * older MIT clients need this, we might have to insert more
544          * complex code */
545
546         nt_status = gensec_set_local_address(gensec_security, peer_addr);
547         if (!NT_STATUS_IS_OK(nt_status)) {
548                 talloc_free(tmp_ctx);
549                 return KDC_PROCESS_FAILED;
550         }
551 #endif
552
553         nt_status = gensec_set_local_address(gensec_security, my_addr);
554         if (!NT_STATUS_IS_OK(nt_status)) {
555                 talloc_free(tmp_ctx);
556                 return KDC_PROCESS_FAILED;
557         }
558
559         /* We want the GENSEC wrap calls to generate PRIV tokens */
560         gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
561
562         nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
563         if (!NT_STATUS_IS_OK(nt_status)) {
564                 talloc_free(tmp_ctx);
565                 return KDC_PROCESS_FAILED;
566         }
567
568         /* Accept the AP-REQ and generate teh AP-REP we need for the reply */
569         nt_status = gensec_update(gensec_security, tmp_ctx, ap_req, &ap_rep);
570         if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
571
572                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
573                                                        KRB5_KPASSWD_HARDERROR,
574                                                        talloc_asprintf(mem_ctx,
575                                                                        "gensec_update failed: %s",
576                                                                        nt_errstr(nt_status)),
577                                                        &krb_priv_rep);
578                 ap_rep.length = 0;
579                 if (ret) {
580                         goto reply;
581                 }
582                 talloc_free(tmp_ctx);
583                 return KDC_PROCESS_FAILED;
584         }
585
586         /* Extract the data from the KRB-PRIV half of the message */
587         nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
588         if (!NT_STATUS_IS_OK(nt_status)) {
589                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
590                                                        KRB5_KPASSWD_HARDERROR,
591                                                        talloc_asprintf(mem_ctx,
592                                                                        "gensec_unwrap failed: %s",
593                                                                        nt_errstr(nt_status)),
594                                                        &krb_priv_rep);
595                 ap_rep.length = 0;
596                 if (ret) {
597                         goto reply;
598                 }
599                 talloc_free(tmp_ctx);
600                 return KDC_PROCESS_FAILED;
601         }
602
603         /* Figure out something to do with it (probably changing a password...) */
604         ret = kpasswd_process_request(kdc, tmp_ctx,
605                                       gensec_security,
606                                       version,
607                                       &kpasswd_req, &kpasswd_rep);
608         if (!ret) {
609                 /* Argh! */
610                 talloc_free(tmp_ctx);
611                 return KDC_PROCESS_FAILED;
612         }
613
614         /* And wrap up the reply: This ensures that the error message
615          * or success can be verified by the client */
616         nt_status = gensec_wrap(gensec_security, tmp_ctx,
617                                 &kpasswd_rep, &krb_priv_rep);
618         if (!NT_STATUS_IS_OK(nt_status)) {
619                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx,
620                                                        KRB5_KPASSWD_HARDERROR,
621                                                        talloc_asprintf(mem_ctx,
622                                                                        "gensec_wrap failed: %s",
623                                                                        nt_errstr(nt_status)),
624                                                        &krb_priv_rep);
625                 ap_rep.length = 0;
626                 if (ret) {
627                         goto reply;
628                 }
629                 talloc_free(tmp_ctx);
630                 return KDC_PROCESS_FAILED;
631         }
632
633 reply:
634         *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
635         if (!reply->data) {
636                 talloc_free(tmp_ctx);
637                 return KDC_PROCESS_FAILED;
638         }
639
640         RSSVAL(reply->data, 0, reply->length);
641         RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
642         RSSVAL(reply->data, 4, ap_rep.length);
643         memcpy(reply->data + header_len,
644                ap_rep.data,
645                ap_rep.length);
646         memcpy(reply->data + header_len + ap_rep.length,
647                krb_priv_rep.data,
648                krb_priv_rep.length);
649
650         talloc_free(tmp_ctx);
651         return KDC_PROCESS_OK;
652 }
653