r13107: Follow the lead of Heimdal's kpasswdd and use the HDB (hdb-ldb in our
[samba.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 2 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, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25 #include "smbd/service_task.h"
26 #include "lib/events/events.h"
27 #include "lib/socket/socket.h"
28 #include "kdc/kdc.h"
29 #include "system/network.h"
30 #include "dlinklist.h"
31 #include "lib/ldb/include/ldb.h"
32 #include "heimdal/lib/krb5/krb5-private.h"
33 #include "auth/auth.h"
34 #include "dsdb/samdb/samdb.h"
35
36 /* hold information about one kdc socket */
37 struct kpasswd_socket {
38         struct socket_context *sock;
39         struct kdc_server *kdc;
40         struct fd_event *fde;
41
42         /* a queue of outgoing replies that have been deferred */
43         struct kdc_reply *send_queue;
44 };
45
46 /* Return true if there is a valid error packet formed in the error_blob */
47 static BOOL kpasswdd_make_error_reply(struct kdc_server *kdc, 
48                                      TALLOC_CTX *mem_ctx, 
49                                      uint16_t result_code, 
50                                      const char *error_string, 
51                                      DATA_BLOB *error_blob) 
52 {
53         char *error_string_utf8;
54         ssize_t len;
55         
56         DEBUG(result_code ? 3 : 10, ("kpasswdd: %s\n", error_string));
57
58         len = push_utf8_talloc(mem_ctx, &error_string_utf8, error_string);
59         if (len == -1) {
60                 return False;
61         }
62
63         *error_blob = data_blob_talloc(mem_ctx, NULL, 2 + len + 1);
64         if (!error_blob->data) {
65                 return False;
66         }
67         RSSVAL(error_blob->data, 0, result_code);
68         memcpy(error_blob->data + 2, error_string_utf8, len + 1);
69         return True;
70 }
71
72 /* Return true if there is a valid error packet formed in the error_blob */
73 static BOOL kpasswdd_make_unauth_error_reply(struct kdc_server *kdc, 
74                                             TALLOC_CTX *mem_ctx, 
75                                             uint16_t result_code, 
76                                             const char *error_string, 
77                                             DATA_BLOB *error_blob) 
78 {
79         BOOL ret;
80         int kret;
81         DATA_BLOB error_bytes;
82         krb5_data k5_error_bytes, k5_error_blob;
83         ret = kpasswdd_make_error_reply(kdc, mem_ctx, result_code, error_string, 
84                                        &error_bytes);
85         if (!ret) {
86                 return False;
87         }
88         k5_error_bytes.data = error_bytes.data;
89         k5_error_bytes.length = error_bytes.length;
90         kret = krb5_mk_error(kdc->smb_krb5_context->krb5_context,
91                              result_code, NULL, &k5_error_bytes, 
92                              NULL, NULL, NULL, NULL, &k5_error_blob);
93         if (kret) {
94                 return False;
95         }
96         *error_blob = data_blob_talloc(mem_ctx, k5_error_blob.data, k5_error_blob.length);
97         krb5_data_free(&k5_error_blob);
98         if (!error_blob->data) {
99                 return False;
100         }
101         return True;
102 }
103
104 static BOOL kpasswd_make_pwchange_reply(struct kdc_server *kdc, 
105                                         TALLOC_CTX *mem_ctx, 
106                                         NTSTATUS status, 
107                                         enum samr_RejectReason reject_reason,
108                                         struct samr_DomInfo1 *dominfo,
109                                         DATA_BLOB *error_blob) 
110 {
111         if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
112                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
113                                                 KRB5_KPASSWD_ACCESSDENIED,
114                                                 "No such user when changing password",
115                                                 error_blob);
116         }
117         if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
118                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
119                                                 KRB5_KPASSWD_ACCESSDENIED,
120                                                 "Not permitted to change password",
121                                                 error_blob);
122         }
123         if (NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION)) {
124                 const char *reject_string;
125                 switch (reject_reason) {
126                 case SAMR_REJECT_TOO_SHORT:
127                         reject_string = talloc_asprintf(mem_ctx, "Password too short, password must be at least %d characters long",
128                                                         dominfo->min_password_length);
129                         break;
130                 case SAMR_REJECT_COMPLEXITY:
131                         reject_string = "Password does not meet complexity requirements";
132                         break;
133                 case SAMR_REJECT_OTHER:
134                 default:
135                         reject_string = talloc_asprintf(mem_ctx, "Password must be at least %d characters long, and cannot match any of your %d previous passwords",
136                                                         dominfo->min_password_length, dominfo->password_history_length);
137                         break;
138                 }
139                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
140                                                 KRB5_KPASSWD_SOFTERROR,
141                                                 reject_string,
142                                                 error_blob);
143         }
144         if (!NT_STATUS_IS_OK(status)) {
145                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
146                                                  KRB5_KPASSWD_HARDERROR,
147                                                  talloc_asprintf(mem_ctx, "failed to set password: %s", nt_errstr(status)),
148                                                  error_blob);
149                 
150         }
151         return kpasswdd_make_error_reply(kdc, mem_ctx, KRB5_KPASSWD_SUCCESS,
152                                         "Password changed",
153                                         error_blob);
154 }
155
156 /* 
157    A user password change
158    
159    Return true if there is a valid error packet (or sucess) formed in
160    the error_blob
161 */
162 static BOOL kpasswdd_change_password(struct kdc_server *kdc,
163                                      TALLOC_CTX *mem_ctx, 
164                                      struct auth_session_info *session_info,
165                                      const char *password,
166                                      DATA_BLOB *reply)
167 {
168         NTSTATUS status;
169         enum samr_RejectReason reject_reason;
170         struct samr_DomInfo1 *dominfo;
171         struct ldb_context *samdb;
172
173         samdb = samdb_connect(mem_ctx, system_session(mem_ctx));
174         if (!samdb) {
175                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
176                                                 KRB5_KPASSWD_HARDERROR,
177                                                 "Failed to open samdb",
178                                                 reply);
179         }
180         
181         DEBUG(3, ("Changing password of %s\\%s (%s)\n", 
182                   session_info->server_info->domain_name,
183                   session_info->server_info->account_name,
184                   dom_sid_string(mem_ctx, session_info->security_token->user_sid)));
185
186         /* User password change */
187         status = samdb_set_password_sid(samdb, mem_ctx, 
188                                         session_info->security_token->user_sid,
189                                         password, NULL, NULL, 
190                                         True, /* this is a user password change */
191                                         True, /* run restriction tests */
192                                         &reject_reason,
193                                         &dominfo);
194         return kpasswd_make_pwchange_reply(kdc, mem_ctx, 
195                                            status, 
196                                            reject_reason,
197                                            dominfo, 
198                                            reply);
199
200 }
201
202 static BOOL kpasswd_process_request(struct kdc_server *kdc,
203                                     TALLOC_CTX *mem_ctx, 
204                                     struct gensec_security *gensec_security,
205                                     uint16_t version,
206                                     DATA_BLOB *input, 
207                                     DATA_BLOB *reply)
208 {
209         struct auth_session_info *session_info;
210         if (!NT_STATUS_IS_OK(gensec_session_info(gensec_security, 
211                                                  &session_info))) {
212                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
213                                                 KRB5_KPASSWD_HARDERROR,
214                                                 "gensec_session_info failed!",
215                                                 reply);
216         }
217
218         switch (version) {
219         case KRB5_KPASSWD_VERS_CHANGEPW:
220         {
221                 char *password = talloc_strndup(mem_ctx, (const char *)input->data, input->length);
222                 if (!password) {
223                         return False;
224                 }
225                 return kpasswdd_change_password(kdc, mem_ctx, session_info, 
226                                                 password, reply);
227                 break;
228         }
229         case KRB5_KPASSWD_VERS_SETPW:
230         {
231                 NTSTATUS status;
232                 enum samr_RejectReason reject_reason;
233                 struct samr_DomInfo1 *dominfo;
234                 struct ldb_context *samdb;
235                 struct ldb_message *msg;
236                 krb5_context context = kdc->smb_krb5_context->krb5_context;
237
238                 ChangePasswdDataMS chpw;
239                 char *password;
240
241                 krb5_principal principal;
242                 char *set_password_on_princ;
243                 struct ldb_dn *set_password_on_dn;
244
245                 size_t len;
246                 int ret;
247
248                 msg = ldb_msg_new(mem_ctx);
249                 if (!msg) {
250                         return False;
251                 }
252
253                 ret = decode_ChangePasswdDataMS(input->data, input->length,
254                                                 &chpw, &len);
255                 if (ret) {
256                         return kpasswdd_make_error_reply(kdc, mem_ctx, 
257                                                         KRB5_KPASSWD_MALFORMED,
258                                                         "failed to decode password change structure",
259                                                         reply);
260                 }
261                 
262                 password = talloc_strndup(mem_ctx, chpw.newpasswd.data, 
263                                           chpw.newpasswd.length);
264                 if (!password) {
265                         free_ChangePasswdDataMS(&chpw);
266                         return False;
267                 }
268                 if ((chpw.targname && !chpw.targrealm) 
269                     || (!chpw.targname && chpw.targrealm)) {
270                         return kpasswdd_make_error_reply(kdc, mem_ctx, 
271                                                         KRB5_KPASSWD_MALFORMED,
272                                                         "Realm and principal must be both present, or neither present",
273                                                         reply);
274                 }
275                 if (chpw.targname && chpw.targrealm) {
276                         if (_krb5_principalname2krb5_principal(&principal, *chpw.targname, 
277                                                                *chpw.targrealm) != 0) {
278                                 free_ChangePasswdDataMS(&chpw);
279                                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
280                                                                 KRB5_KPASSWD_MALFORMED,
281                                                                 "failed to extract principal to set",
282                                                                 reply);
283                                 
284                         }
285                 } else {
286                         free_ChangePasswdDataMS(&chpw);
287                         return kpasswdd_change_password(kdc, mem_ctx, session_info, 
288                                                         password, reply);
289                 }
290                 free_ChangePasswdDataMS(&chpw);
291
292                 if (krb5_unparse_name(context, principal, &set_password_on_princ) != 0) {
293                         krb5_free_principal(context, principal);
294                         return kpasswdd_make_error_reply(kdc, mem_ctx, 
295                                                         KRB5_KPASSWD_MALFORMED,
296                                                         "krb5_unparse_name failed!",
297                                                         reply);
298                 }
299                 
300                 krb5_free_principal(context, principal);
301                 
302                 samdb = samdb_connect(mem_ctx, session_info);
303                 if (!samdb) {
304                         return kpasswdd_make_error_reply(kdc, mem_ctx, 
305                                                          KRB5_KPASSWD_HARDERROR,
306                                                          "Unable to open database!",
307                                                          reply);
308                 }
309
310                 DEBUG(3, ("%s\\%s (%s) is changing password of %s\n", 
311                           session_info->server_info->domain_name,
312                           session_info->server_info->account_name,
313                           dom_sid_string(mem_ctx, session_info->security_token->user_sid), 
314                           set_password_on_princ));
315                 ret = ldb_transaction_start(samdb);
316                 if (ret) {
317                         status = NT_STATUS_TRANSACTION_ABORTED;
318                         return kpasswd_make_pwchange_reply(kdc, mem_ctx, 
319                                                            status,
320                                                            reject_reason, 
321                                                            dominfo, 
322                                                            reply);
323                 }
324
325                 status = crack_user_principal_name(samdb, mem_ctx, 
326                                                    set_password_on_princ, 
327                                                    &set_password_on_dn, NULL);
328                 free(set_password_on_princ);
329                 if (!NT_STATUS_IS_OK(status)) {
330                         ldb_transaction_cancel(samdb);
331                         return kpasswd_make_pwchange_reply(kdc, mem_ctx, 
332                                                            status,
333                                                            reject_reason, 
334                                                            dominfo, 
335                                                            reply);
336                 }
337
338                 msg = ldb_msg_new(mem_ctx);
339                 if (msg == NULL) {
340                         ldb_transaction_cancel(samdb);
341                         status = NT_STATUS_NO_MEMORY;
342                 } else {
343                         msg->dn = ldb_dn_copy(msg, set_password_on_dn);
344                         if (!msg->dn) {
345                                 status = NT_STATUS_NO_MEMORY;
346                         }
347                 }
348
349                 if (NT_STATUS_IS_OK(status)) {
350                         /* Admin password set */
351                         status = samdb_set_password(samdb, mem_ctx,
352                                                     set_password_on_dn, NULL,
353                                                     msg, password, NULL, NULL, 
354                                                     False, /* this is not a user password change */
355                                                     True, /* run restriction tests */
356                                                     &reject_reason, &dominfo);
357                 }
358
359                 if (NT_STATUS_IS_OK(status)) {
360                         /* modify the samdb record */
361                         ret = samdb_replace(samdb, mem_ctx, msg);
362                         if (ret != 0) {
363                                 DEBUG(2,("Failed to modify record to set password on %s: %s\n",
364                                          ldb_dn_linearize(mem_ctx, msg->dn),
365                                          ldb_errstring(samdb)));
366                                 status = NT_STATUS_ACCESS_DENIED;
367                         }
368                 }
369                 if (NT_STATUS_IS_OK(status)) {
370                         ret = ldb_transaction_commit(samdb);
371                         if (ret != 0) {
372                                 DEBUG(1,("Failed to commit transaction to set password on %s: %s\n",
373                                          ldb_dn_linearize(mem_ctx, msg->dn),
374                                          ldb_errstring(samdb)));
375                                 status = NT_STATUS_TRANSACTION_ABORTED;
376                         }
377                 } else {
378                         ldb_transaction_cancel(samdb);
379                 }
380                 return kpasswd_make_pwchange_reply(kdc, mem_ctx, 
381                                                    status,
382                                                    reject_reason, 
383                                                    dominfo, 
384                                                    reply);
385         }
386         default:
387                 return kpasswdd_make_error_reply(kdc, mem_ctx, 
388                                                  KRB5_KPASSWD_BAD_VERSION,
389                                                  talloc_asprintf(mem_ctx, 
390                                                                  "Protocol version %u not supported", 
391                                                                  version),
392                                                  reply);
393         }
394         return True;
395 }
396
397 BOOL kpasswdd_process(struct kdc_server *kdc,
398                       TALLOC_CTX *mem_ctx, 
399                       DATA_BLOB *input, 
400                       DATA_BLOB *reply,
401                       struct socket_address *peer_addr,
402                       struct socket_address *my_addr)
403 {
404         BOOL ret;
405         const uint16_t header_len = 6;
406         uint16_t len;
407         uint16_t ap_req_len;
408         uint16_t krb_priv_len;
409         uint16_t version;
410         NTSTATUS nt_status;
411         DATA_BLOB ap_req, krb_priv_req, krb_priv_rep, ap_rep;
412         DATA_BLOB kpasswd_req, kpasswd_rep;
413         struct cli_credentials *server_credentials;
414         struct gensec_security *gensec_security;
415         TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
416         
417         if (!tmp_ctx) {
418                 return False;
419         }
420
421         /* Be parinoid.  We need to ensure we don't just let the
422          * caller lead us into a buffer overflow */
423         if (input->length <= header_len) {
424                 talloc_free(tmp_ctx);
425                 return False;
426         }
427
428         len = RSVAL(input->data, 0);
429         if (input->length != len) {
430                 talloc_free(tmp_ctx);
431                 return False;
432         }
433
434         /* There are two different versions of this protocol so far,
435          * plus others in the standards pipe.  Fortunetly they all
436          * take a very similar framing */
437         version = RSVAL(input->data, 2);
438         ap_req_len = RSVAL(input->data, 4);
439         if ((ap_req_len >= len) || (ap_req_len + header_len) >= len) {
440                 talloc_free(tmp_ctx);
441                 return False;
442         }
443         
444         krb_priv_len = len - ap_req_len;
445         ap_req = data_blob_const(&input->data[header_len], ap_req_len);
446         krb_priv_req = data_blob_const(&input->data[header_len + ap_req_len], krb_priv_len);
447         
448         nt_status = gensec_server_start(tmp_ctx, &gensec_security, kdc->task->event_ctx);
449         if (!NT_STATUS_IS_OK(nt_status)) {
450                 talloc_free(tmp_ctx);
451                 return False;
452         }
453
454         server_credentials 
455                 = cli_credentials_init(tmp_ctx);
456         if (!server_credentials) {
457                 DEBUG(1, ("Failed to init server credentials\n"));
458                 return False;
459         }
460
461         /* We want the credentials subsystem to use the krb5 context
462          * we already have, rather than a new context */        
463         cli_credentials_set_krb5_context(server_credentials, kdc->smb_krb5_context);
464         cli_credentials_set_conf(server_credentials);
465         nt_status = cli_credentials_set_stored_principal(server_credentials, "kadmin/changepw");
466         if (!NT_STATUS_IS_OK(nt_status)) {
467                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx, 
468                                                        KRB5_KPASSWD_HARDERROR,
469                                                        talloc_asprintf(mem_ctx, 
470                                                                        "Failed to obtain server credentials for kadmin/changepw: %s\n", 
471                                                                        nt_errstr(nt_status)),
472                                                        &krb_priv_rep);
473                 ap_rep.length = 0;
474                 if (ret) {
475                         goto reply;
476                 }
477                 talloc_free(tmp_ctx);
478                 return ret;
479         }
480         
481         nt_status = gensec_set_credentials(gensec_security, server_credentials);
482         if (!NT_STATUS_IS_OK(nt_status)) {
483                 talloc_free(tmp_ctx);
484                 return False;
485         }
486
487         /* The kerberos PRIV packets include these addresses.  MIT
488          * clients check that they are present */
489         nt_status = gensec_set_peer_addr(gensec_security, peer_addr);
490         if (!NT_STATUS_IS_OK(nt_status)) {
491                 talloc_free(tmp_ctx);
492                 return False;
493         }
494         nt_status = gensec_set_my_addr(gensec_security, my_addr);
495         if (!NT_STATUS_IS_OK(nt_status)) {
496                 talloc_free(tmp_ctx);
497                 return False;
498         }
499
500         /* We want the GENSEC wrap calls to generate PRIV tokens */
501         gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
502
503         nt_status = gensec_start_mech_by_name(gensec_security, "krb5");
504         if (!NT_STATUS_IS_OK(nt_status)) {
505                 talloc_free(tmp_ctx);
506                 return False;
507         }
508
509         /* Accept the AP-REQ and generate teh AP-REP we need for the reply */
510         nt_status = gensec_update(gensec_security, tmp_ctx, ap_req, &ap_rep);
511         if (!NT_STATUS_IS_OK(nt_status) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
512                 
513                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx, 
514                                                        KRB5_KPASSWD_HARDERROR,
515                                                        talloc_asprintf(mem_ctx, 
516                                                                        "gensec_update failed: %s", 
517                                                                        nt_errstr(nt_status)),
518                                                        &krb_priv_rep);
519                 ap_rep.length = 0;
520                 if (ret) {
521                         goto reply;
522                 }
523                 talloc_free(tmp_ctx);
524                 return ret;
525         }
526
527         /* Extract the data from the KRB-PRIV half of the message */
528         nt_status = gensec_unwrap(gensec_security, tmp_ctx, &krb_priv_req, &kpasswd_req);
529         if (!NT_STATUS_IS_OK(nt_status)) {
530                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx, 
531                                                        KRB5_KPASSWD_HARDERROR,
532                                                        talloc_asprintf(mem_ctx, 
533                                                                        "gensec_unwrap failed: %s", 
534                                                                        nt_errstr(nt_status)),
535                                                        &krb_priv_rep);
536                 ap_rep.length = 0;
537                 if (ret) {
538                         goto reply;
539                 }
540                 talloc_free(tmp_ctx);
541                 return ret;
542         }
543
544         /* Figure out something to do with it (probably changing a password...) */
545         ret = kpasswd_process_request(kdc, tmp_ctx, 
546                                       gensec_security, 
547                                       version, 
548                                       &kpasswd_req, &kpasswd_rep); 
549         if (!ret) {
550                 /* Argh! */
551                 return False;
552         }
553
554         /* And wrap up the reply: This ensures that the error message
555          * or success can be verified by the client */
556         nt_status = gensec_wrap(gensec_security, tmp_ctx, 
557                                 &kpasswd_rep, &krb_priv_rep);
558         if (!NT_STATUS_IS_OK(nt_status)) {
559                 ret = kpasswdd_make_unauth_error_reply(kdc, mem_ctx, 
560                                                        KRB5_KPASSWD_HARDERROR,
561                                                        talloc_asprintf(mem_ctx, 
562                                                                        "gensec_wrap failed: %s", 
563                                                                        nt_errstr(nt_status)),
564                                                        &krb_priv_rep);
565                 ap_rep.length = 0;
566                 if (ret) {
567                         goto reply;
568                 }
569                 talloc_free(tmp_ctx);
570                 return ret;
571         }
572         
573 reply:
574         *reply = data_blob_talloc(mem_ctx, NULL, krb_priv_rep.length + ap_rep.length + header_len);
575         if (!reply->data) {
576                 return False;
577         }
578
579         RSSVAL(reply->data, 0, reply->length);
580         RSSVAL(reply->data, 2, 1); /* This is a version 1 reply, MS change/set or otherwise */
581         RSSVAL(reply->data, 4, ap_rep.length);
582         memcpy(reply->data + header_len, 
583                ap_rep.data, 
584                ap_rep.length);
585         memcpy(reply->data + header_len + ap_rep.length, 
586                krb_priv_rep.data, 
587                krb_priv_rep.length);
588
589         talloc_free(tmp_ctx);
590         return ret;
591 }
592