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