s4:kdc: Cache user info and resource groups from PACs
[samba.git] / source4 / kdc / kpasswd-service.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Samba kpasswd implementation
5
6    Copyright (c) 2005      Andrew Bartlett <abartlet@samba.org>
7    Copyright (c) 2016      Andreas Schneider <asn@samba.org>
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 "samba/service_task.h"
25 #include "tsocket/tsocket.h"
26 #include "auth/credentials/credentials.h"
27 #include "auth/auth.h"
28 #include "auth/gensec/gensec.h"
29 #include "kdc/kdc-server.h"
30 #include "kdc/kpasswd-service.h"
31 #include "kdc/kpasswd-helper.h"
32 #include "param/param.h"
33
34 #undef DBGC_CLASS
35 #define DBGC_CLASS DBGC_KERBEROS
36
37 #define HEADER_LEN 6
38 #ifndef RFC3244_VERSION
39 #define RFC3244_VERSION 0xff80
40 #endif
41
42 kdc_code kpasswd_process(struct kdc_server *kdc,
43                          TALLOC_CTX *mem_ctx,
44                          DATA_BLOB *request,
45                          DATA_BLOB *reply,
46                          struct tsocket_address *remote_addr,
47                          struct tsocket_address *local_addr,
48                          int datagram)
49 {
50         uint16_t len;
51         uint16_t verno;
52         uint16_t ap_req_len;
53         uint16_t enc_data_len;
54         DATA_BLOB ap_req_blob = data_blob_null;
55         DATA_BLOB ap_rep_blob = data_blob_null;
56         DATA_BLOB enc_data_blob = data_blob_null;
57         DATA_BLOB dec_data_blob = data_blob_null;
58         DATA_BLOB kpasswd_dec_reply = data_blob_null;
59         const char *error_string = NULL;
60         krb5_error_code error_code = 0;
61         struct cli_credentials *server_credentials;
62         struct gensec_security *gensec_security;
63 #ifndef SAMBA4_USES_HEIMDAL
64         struct sockaddr_storage remote_ss;
65 #endif
66         struct sockaddr_storage local_ss;
67         ssize_t socklen;
68         TALLOC_CTX *tmp_ctx;
69         kdc_code rc = KDC_ERROR;
70         krb5_error_code code = 0;
71         NTSTATUS status;
72         int rv;
73         bool is_inet;
74         bool ok;
75
76         if (kdc->am_rodc) {
77                 return KDC_PROXY_REQUEST;
78         }
79
80         tmp_ctx = talloc_new(mem_ctx);
81         if (tmp_ctx == NULL) {
82                 return KDC_ERROR;
83         }
84
85         is_inet = tsocket_address_is_inet(remote_addr, "ip");
86         if (!is_inet) {
87                 DBG_WARNING("Invalid remote IP address\n");
88                 goto done;
89         }
90
91 #ifndef SAMBA4_USES_HEIMDAL
92         /*
93          * FIXME: Heimdal fails to to do a krb5_rd_req() in gensec_krb5 if we
94          * set the remote address.
95          */
96
97         /* remote_addr */
98         socklen = tsocket_address_bsd_sockaddr(remote_addr,
99                                                (struct sockaddr *)&remote_ss,
100                                                sizeof(struct sockaddr_storage));
101         if (socklen < 0) {
102                 DBG_WARNING("Invalid remote IP address\n");
103                 goto done;
104         }
105 #endif
106
107         /* local_addr */
108         socklen = tsocket_address_bsd_sockaddr(local_addr,
109                                                (struct sockaddr *)&local_ss,
110                                                sizeof(struct sockaddr_storage));
111         if (socklen < 0) {
112                 DBG_WARNING("Invalid local IP address\n");
113                 goto done;
114         }
115
116         if (request->length <= HEADER_LEN) {
117                 DBG_WARNING("Request truncated\n");
118                 goto done;
119         }
120
121         len = RSVAL(request->data, 0);
122         if (request->length != len) {
123                 DBG_WARNING("Request length does not match\n");
124                 goto done;
125         }
126
127         verno = RSVAL(request->data, 2);
128         if (verno != 1 && verno != RFC3244_VERSION) {
129                 DBG_WARNING("Unsupported version: 0x%04x\n", verno);
130         }
131
132         ap_req_len = RSVAL(request->data, 4);
133         if ((ap_req_len >= len) || ((ap_req_len + HEADER_LEN) >= len)) {
134                 DBG_WARNING("AP_REQ truncated\n");
135                 goto done;
136         }
137
138         ap_req_blob = data_blob_const(&request->data[HEADER_LEN], ap_req_len);
139
140         enc_data_len = len - ap_req_len;
141         enc_data_blob = data_blob_const(&request->data[HEADER_LEN + ap_req_len],
142                                         enc_data_len);
143
144         server_credentials = cli_credentials_init(tmp_ctx);
145         if (server_credentials == NULL) {
146                 DBG_ERR("Failed to initialize server credentials!\n");
147                 goto done;
148         }
149
150         /*
151          * We want the credentials subsystem to use the krb5 context we already
152          * have, rather than a new context.
153          *
154          * On this context the KDB plugin has been loaded, so we can access
155          * dsdb.
156          */
157         status = cli_credentials_set_krb5_context(server_credentials,
158                                                   kdc->smb_krb5_context);
159         if (!NT_STATUS_IS_OK(status)) {
160                 goto done;
161         }
162
163         ok = cli_credentials_set_conf(server_credentials, kdc->task->lp_ctx);
164         if (!ok) {
165                 goto done;
166         }
167
168         /*
169          * After calling cli_credentials_set_conf(), explicitly set the realm
170          * with CRED_SPECIFIED. We need to do this so the result of
171          * principal_from_credentials() called from the gensec layer is
172          * CRED_SPECIFIED rather than CRED_SMB_CONF, avoiding a fallback to
173          * match-by-key (very undesirable in this case).
174          */
175         ok = cli_credentials_set_realm(server_credentials,
176                                        lpcfg_realm(kdc->task->lp_ctx),
177                                        CRED_SPECIFIED);
178         if (!ok) {
179                 goto done;
180         }
181
182         ok = cli_credentials_set_username(server_credentials,
183                                           "kadmin/changepw",
184                                           CRED_SPECIFIED);
185         if (!ok) {
186                 goto done;
187         }
188
189         /* Check that the server principal is indeed CRED_SPECIFIED. */
190         {
191                 char *principal = NULL;
192                 enum credentials_obtained obtained;
193
194                 principal = cli_credentials_get_principal_and_obtained(server_credentials,
195                                                                        tmp_ctx,
196                                                                        &obtained);
197                 if (obtained < CRED_SPECIFIED) {
198                         goto done;
199                 }
200
201                 TALLOC_FREE(principal);
202         }
203
204         rv = cli_credentials_set_keytab_name(server_credentials,
205                                              kdc->task->lp_ctx,
206                                              kdc->kpasswd_keytab_name,
207                                              CRED_SPECIFIED);
208         if (rv != 0) {
209                 DBG_ERR("Failed to set credentials keytab name\n");
210                 goto done;
211         }
212
213         status = samba_server_gensec_start(tmp_ctx,
214                                            kdc->task->event_ctx,
215                                            kdc->task->msg_ctx,
216                                            kdc->task->lp_ctx,
217                                            server_credentials,
218                                            "kpasswd",
219                                            &gensec_security);
220         if (!NT_STATUS_IS_OK(status)) {
221                 goto done;
222         }
223
224         status = gensec_set_local_address(gensec_security, local_addr);
225         if (!NT_STATUS_IS_OK(status)) {
226                 goto done;
227         }
228
229 #ifndef SAMBA4_USES_HEIMDAL
230         status = gensec_set_remote_address(gensec_security, remote_addr);
231         if (!NT_STATUS_IS_OK(status)) {
232                 goto done;
233         }
234 #endif
235
236         /* We want the GENSEC wrap calls to generate PRIV tokens */
237         gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL);
238
239         /* Use the krb5 gesec mechanism so we can load DB modules */
240         status = gensec_start_mech_by_name(gensec_security, "krb5");
241         if (!NT_STATUS_IS_OK(status)) {
242                 goto done;
243         }
244
245         /*
246          * Accept the AP-REQ and generate the AP-REP we need for the reply
247          *
248          * We only allow KRB5 and make sure the backend to is RPC/IPC free.
249          *
250          * See gensec_krb5_update_internal() as GENSEC_SERVER.
251          *
252          * It allows gensec_update() not to block.
253          *
254          * If that changes in future we need to use
255          * gensec_update_send/recv here!
256          */
257         status = gensec_update(gensec_security, tmp_ctx,
258                                ap_req_blob, &ap_rep_blob);
259         if (!NT_STATUS_IS_OK(status) &&
260             !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
261                 ap_rep_blob = data_blob_null;
262                 error_code = KRB5_KPASSWD_HARDERROR;
263                 error_string = talloc_asprintf(tmp_ctx,
264                                                "gensec_update failed - %s\n",
265                                                nt_errstr(status));
266                 DBG_ERR("%s", error_string);
267                 goto reply;
268         }
269
270         status = gensec_unwrap(gensec_security,
271                                tmp_ctx,
272                                &enc_data_blob,
273                                &dec_data_blob);
274         if (!NT_STATUS_IS_OK(status)) {
275                 ap_rep_blob = data_blob_null;
276                 error_code = KRB5_KPASSWD_HARDERROR;
277                 error_string = talloc_asprintf(tmp_ctx,
278                                                "gensec_unwrap failed - %s\n",
279                                                nt_errstr(status));
280                 DBG_ERR("%s", error_string);
281                 goto reply;
282         }
283
284         code = kpasswd_handle_request(kdc,
285                                       tmp_ctx,
286                                       gensec_security,
287                                       verno,
288                                       &dec_data_blob,
289                                       &kpasswd_dec_reply,
290                                       &error_string);
291         if (code != 0) {
292                 ap_rep_blob = data_blob_null;
293                 error_code = code;
294                 goto reply;
295         }
296
297         status = gensec_wrap(gensec_security,
298                              tmp_ctx,
299                              &kpasswd_dec_reply,
300                              &enc_data_blob);
301         if (!NT_STATUS_IS_OK(status)) {
302                 ap_rep_blob = data_blob_null;
303                 error_code = KRB5_KPASSWD_HARDERROR;
304                 error_string = talloc_asprintf(tmp_ctx,
305                                                "gensec_wrap failed - %s\n",
306                                                nt_errstr(status));
307                 DBG_ERR("%s", error_string);
308                 goto reply;
309         }
310
311 reply:
312         if (error_code != 0) {
313                 krb5_data k_enc_data;
314                 krb5_data k_dec_data;
315                 const char *principal_string;
316                 krb5_principal server_principal;
317
318                 if (error_string == NULL) {
319                         DBG_ERR("Invalid error string! This should not happen\n");
320                         goto done;
321                 }
322
323                 ok = kpasswd_make_error_reply(tmp_ctx,
324                                               error_code,
325                                               error_string,
326                                               &dec_data_blob);
327                 if (!ok) {
328                         DBG_ERR("Failed to create error reply\n");
329                         goto done;
330                 }
331
332                 k_dec_data = smb_krb5_data_from_blob(dec_data_blob);
333
334                 principal_string = cli_credentials_get_principal(server_credentials,
335                                                                  tmp_ctx);
336                 if (principal_string == NULL) {
337                         goto done;
338                 }
339
340                 code = smb_krb5_parse_name(kdc->smb_krb5_context->krb5_context,
341                                            principal_string,
342                                            &server_principal);
343                 if (code != 0) {
344                         DBG_ERR("Failed to create principal: %s\n",
345                                 error_message(code));
346                         goto done;
347                 }
348
349                 code = smb_krb5_mk_error(kdc->smb_krb5_context->krb5_context,
350                                          KRB5KDC_ERR_NONE + error_code,
351                                          NULL, /* e_text */
352                                          &k_dec_data,
353                                          NULL, /* client */
354                                          server_principal,
355                                          &k_enc_data);
356                 krb5_free_principal(kdc->smb_krb5_context->krb5_context,
357                                     server_principal);
358                 if (code != 0) {
359                         DBG_ERR("Failed to create krb5 error reply: %s\n",
360                                 error_message(code));
361                         goto done;
362                 }
363
364                 enc_data_blob = data_blob_talloc(tmp_ctx,
365                                                  k_enc_data.data,
366                                                  k_enc_data.length);
367                 if (enc_data_blob.data == NULL) {
368                         DBG_ERR("Failed to allocate memory for error reply\n");
369                         goto done;
370                 }
371         }
372
373         *reply = data_blob_talloc(mem_ctx,
374                                   NULL,
375                                   HEADER_LEN + ap_rep_blob.length + enc_data_blob.length);
376         if (reply->data == NULL) {
377                 goto done;
378         }
379         RSSVAL(reply->data, 0, reply->length);
380         RSSVAL(reply->data, 2, 1);
381         RSSVAL(reply->data, 4, ap_rep_blob.length);
382         if (ap_rep_blob.data != NULL) {
383                 memcpy(reply->data + HEADER_LEN,
384                        ap_rep_blob.data,
385                        ap_rep_blob.length);
386         }
387         memcpy(reply->data + HEADER_LEN + ap_rep_blob.length,
388                enc_data_blob.data,
389                enc_data_blob.length);
390
391         rc = KDC_OK;
392 done:
393         talloc_free(tmp_ctx);
394         return rc;
395 }