torture-krb5: Split the expected behaviour of the RODC up
[metze/samba/wip.git] / source4 / torture / krb5 / kdc-canon.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Validate the krb5 pac generation routines
5    
6    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2015
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
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 "system/kerberos.h"
25 #include "torture/smbtorture.h"
26 #include "torture/krb5/proto.h"
27 #include "auth/credentials/credentials.h"
28 #include "lib/cmdline/popt_common.h"
29 #include "source4/auth/kerberos/kerberos.h"
30 #include "source4/auth/kerberos/kerberos_util.h"
31 #include "lib/util/util_net.h"
32
33 #define TEST_CANONICALIZE     0x0000001
34 #define TEST_ENTERPRISE       0x0000002
35 #define TEST_UPPER_REALM      0x0000004
36 #define TEST_UPPER_USERNAME   0x0000008
37 #define TEST_NETBIOS_REALM    0x0000010
38 #define TEST_ALL              0x000001F
39
40 struct test_data {
41         struct smb_krb5_context *smb_krb5_context;
42         const char *realm;
43         const char *real_realm;
44         const char *username;
45         bool canonicalize;
46         bool enterprise;
47         bool upper_realm;
48         bool upper_username;
49 };      
50         
51 struct torture_krb5_context {
52         struct torture_context *tctx;
53         struct addrinfo *server;
54         struct test_data *test_data;
55         int packet_count;
56         AS_REQ as_req;
57         AS_REP as_rep;
58 };
59
60
61 /*
62  * Confirm that the outgoing packet meets certain expectations.  This
63  * should be extended to further assert the correct and expected
64  * behaviour of the krb5 libs, so we know what we are sending to the
65  * server.
66  *
67  * Additionally, this CHANGES the request to remove the canonicalize
68  * flag automatically added by the krb5 libs when an enterprise
69  * principal is used, so we can test what the server does in this
70  * combination.
71  *
72  */
73
74 static bool torture_krb5_pre_send_test(struct torture_krb5_context *test_context, const krb5_data *send_buf, krb5_data *modified_send_buf)
75 {
76         krb5_error_code k5ret;
77         size_t used;
78         torture_assert_int_equal(test_context->tctx,
79                                  decode_AS_REQ(send_buf->data, send_buf->length, &test_context->as_req, &used), 0,
80                                  "decode_AS_REQ failed");
81
82         torture_assert_int_equal(test_context->tctx, used, send_buf->length, "length mismatch");
83         torture_assert_int_equal(test_context->tctx, test_context->as_req.pvno, 5, "Got wrong as_req->pvno");
84         if (test_context->test_data->canonicalize || test_context->test_data->enterprise) {
85                 torture_assert(test_context->tctx, test_context->as_req.req_body.kdc_options.canonicalize, "krb5 libs did not set canonicalize!");
86         } else {
87                 torture_assert_int_equal(test_context->tctx, test_context->as_req.req_body.kdc_options.canonicalize, false, "krb5 libs unexpectedly set canonicalize!");
88         }
89
90         if (test_context->test_data->enterprise) {
91                 torture_assert_int_equal(test_context->tctx, test_context->as_req.req_body.cname->name_type, KRB5_NT_ENTERPRISE_PRINCIPAL, "krb5 libs did not pass principal as enterprise!");
92         } else {
93                 torture_assert_int_equal(test_context->tctx, test_context->as_req.req_body.cname->name_type, KRB5_NT_PRINCIPAL, "krb5 libs unexpectedly set principal as enterprise!");
94         }
95
96         /* Force off canonicalize that was forced on by the krb5 libs */
97         if (test_context->test_data->canonicalize == false && test_context->test_data->enterprise) {
98                 test_context->as_req.req_body.kdc_options.canonicalize = false;
99         }
100
101         ASN1_MALLOC_ENCODE(AS_REQ, modified_send_buf->data, modified_send_buf->length,
102                            &test_context->as_req, &used, k5ret);
103         torture_assert_int_equal(test_context->tctx,
104                                  k5ret, 0,
105                                  "encode_AS_REQ failed");
106         torture_assert_int_equal(test_context->tctx, used, send_buf->length, "re-encode length mismatch");
107         return true;
108 }
109
110 /*
111  * Confirm that the incoming packet from the KDC meets certain
112  * expectations.  This uses a packet count to work out what test we
113  * are in, and where in the test we are, so we can assert on the
114  * expected reply packets from the KDC.
115  *
116  */
117
118 static bool torture_krb5_post_recv_test(struct torture_krb5_context *test_context, const krb5_data *recv_buf)
119 {
120         KRB_ERROR error;
121         size_t used;
122         if (test_context->packet_count == 0) {
123                 torture_assert_int_equal(test_context->tctx,
124                                          decode_KRB_ERROR(recv_buf->data, recv_buf->length, &error, &used), 0,
125                                          "decode_AS_REP failed");
126                 torture_assert_int_equal(test_context->tctx, used, recv_buf->length, "length mismatch");
127                 torture_assert_int_equal(test_context->tctx, error.pvno, 5, "Got wrong error.pvno");
128                 torture_assert_int_equal(test_context->tctx, error.error_code, KRB5KDC_ERR_PREAUTH_REQUIRED - KRB5KDC_ERR_NONE,
129                                          "Got wrong error.error_code");
130                 free_KRB_ERROR(&error);
131         } else if ((decode_KRB_ERROR(recv_buf->data, recv_buf->length, &error, &used) == 0)
132                    && (test_context->packet_count == 1)) {
133                 torture_assert_int_equal(test_context->tctx, used, recv_buf->length, "length mismatch");
134                 torture_assert_int_equal(test_context->tctx, error.pvno, 5, "Got wrong error.pvno");
135                 torture_assert_int_equal(test_context->tctx, error.error_code, KRB5KRB_ERR_RESPONSE_TOO_BIG - KRB5KDC_ERR_NONE,
136                                          "Got wrong error.error_code");
137                 free_KRB_ERROR(&error);
138         } else {
139                 torture_assert_int_equal(test_context->tctx,
140                                          decode_AS_REP(recv_buf->data, recv_buf->length, &test_context->as_rep, &used), 0,
141                                          "decode_AS_REP failed");
142                 torture_assert_int_equal(test_context->tctx, used, recv_buf->length, "length mismatch");
143                 torture_assert_int_equal(test_context->tctx,
144                                          test_context->as_rep.pvno, 5,
145                                          "Got wrong as_rep->pvno");
146                 torture_assert_int_equal(test_context->tctx,
147                                          test_context->as_rep.ticket.tkt_vno, 5,
148                                          "Got wrong as_rep->ticket.tkt_vno");
149                 torture_assert(test_context->tctx,
150                                test_context->as_rep.ticket.enc_part.kvno,
151                                "Did not get a KVNO in test_context->as_rep.ticket.enc_part.kvno");
152                 if (torture_setting_bool(test_context->tctx, "expect_cached_at_rodc", false)) {
153                         torture_assert_int_not_equal(test_context->tctx,
154                                                      *test_context->as_rep.ticket.enc_part.kvno & 0xFFFF0000,
155                                                      0, "Did not get a RODC number in the KVNO");
156                 } else {
157                         torture_assert_int_equal(test_context->tctx,
158                                                  *test_context->as_rep.ticket.enc_part.kvno & 0xFFFF0000,
159                                                  0, "Unexpecedly got a RODC number in the KVNO");
160                 }
161                 free_AS_REP(&test_context->as_rep);
162         }
163         torture_assert(test_context->tctx, test_context->packet_count < 3, "too many packets");
164         free_AS_REQ(&test_context->as_req);
165         return true;
166 }
167
168 /* 
169  * This function is set in torture_krb5_init_context_canon as krb5
170  * send_and_recv function.  This allows us to override what server the
171  * test is aimed at, and to inspect the packets just before they are
172  * sent to the network, and before they are processed on the recv
173  * side.
174  *
175  * The torture_krb5_pre_send_test() and torture_krb5_post_recv_test()
176  * functions are implement the actual tests.
177  *
178  * When this asserts, the caller will get a spurious 'cannot contact
179  * any KDC' message.
180  *
181  */
182 static krb5_error_code smb_krb5_send_and_recv_func_canon_override(krb5_context context,
183                                                                    void *data, /* struct torture_krb5_context */
184                                                                    krb5_krbhst_info *hi,
185                                                                    time_t timeout,
186                                                                    const krb5_data *send_buf,
187                                                                    krb5_data *recv_buf)
188 {
189         krb5_error_code k5ret;
190         bool ok;
191         krb5_data modified_send_buf;
192         
193         struct torture_krb5_context *test_context
194                 = talloc_get_type_abort(data, struct torture_krb5_context);
195
196         ok = torture_krb5_pre_send_test(test_context, send_buf, &modified_send_buf);
197         if (ok == false) {
198                 return EINVAL;
199         }
200         
201         k5ret = smb_krb5_send_and_recv_func_forced(context, test_context->server,
202                                                     hi, timeout, &modified_send_buf, recv_buf);
203
204         ok = torture_krb5_post_recv_test(test_context, recv_buf);
205         if (ok == false) {
206                 return EINVAL;
207         }
208
209         test_context->packet_count++;
210         
211         return k5ret;
212 }
213
214 static int test_context_destructor(struct torture_krb5_context *test_context)
215 {
216         freeaddrinfo(test_context->server);
217         return 0;
218 }
219         
220
221 static bool torture_krb5_init_context_canon(struct torture_context *tctx,
222                                              struct test_data *test_data,
223                                              struct smb_krb5_context **smb_krb5_context)
224 {
225         const char *host = torture_setting_string(tctx, "host", NULL);
226         krb5_error_code k5ret;
227         bool ok;
228
229         struct torture_krb5_context *test_context = talloc_zero(tctx, struct torture_krb5_context);
230         torture_assert(tctx, test_context != NULL, "Failed to allocate");
231
232         test_context->test_data = test_data;
233         test_context->tctx = tctx;
234         
235         k5ret = smb_krb5_init_context(tctx, tctx->lp_ctx, smb_krb5_context);
236         torture_assert_int_equal(tctx, k5ret, 0, "smb_krb5_init_context failed");
237
238         ok = interpret_string_addr_internal(&test_context->server, host, AI_NUMERICHOST);
239         torture_assert(tctx, ok, "Failed to parse target server");
240
241         talloc_set_destructor(test_context, test_context_destructor);
242         
243         set_sockaddr_port(test_context->server->ai_addr, 88);
244
245         k5ret = krb5_set_send_to_kdc_func((*smb_krb5_context)->krb5_context,
246                                           smb_krb5_send_and_recv_func_canon_override,
247                                           test_context);
248         torture_assert_int_equal(tctx, k5ret, 0, "krb5_set_send_to_kdc_func failed");
249         return true;
250 }
251
252
253 static bool torture_krb5_as_req_canon(struct torture_context *tctx, const void *tcase_data)
254 {
255         krb5_error_code k5ret;
256         krb5_get_init_creds_opt *krb_options = NULL;
257         struct test_data *test_data = talloc_get_type_abort(tcase_data, struct test_data);
258         char *realm;
259         char *upper_real_realm;
260         char *username;
261         krb5_principal principal;
262         krb5_principal expected_principal;
263         char *principal_string;
264         int principal_flags;
265         char *expected_principal_string;
266         int expected_principal_flags;
267         char *got_principal_string;
268         char *assertion_message;
269         const char *password = cli_credentials_get_password(cmdline_credentials);
270         struct smb_krb5_context *smb_krb5_context;
271         bool ok;
272         krb5_creds my_creds;
273         
274         ok = torture_krb5_init_context_canon(tctx, test_data, &smb_krb5_context);
275         torture_assert(tctx, ok, "torture_krb5_init_context failed");
276         
277         if (test_data->upper_realm) {
278                 realm = strupper_talloc(test_data, test_data->realm);
279         } else {
280                 realm = strlower_talloc(test_data, test_data->realm);
281         }
282         if (test_data->upper_username) {
283                 username = strupper_talloc(test_data, test_data->username);
284         } else {
285                 username = talloc_strdup(test_data, test_data->username);
286         }
287
288         principal_string = talloc_asprintf(test_data, "%s@%s", username, realm);
289         
290         upper_real_realm = strupper_talloc(test_data, test_data->real_realm);
291
292         /* 
293          * If we are set to canonicalize, we get back the fixed UPPER
294          * case realm, and the real username (ie matching LDAP
295          * samAccountName) 
296          *
297          * Otherwise, if we are set to enterprise, we
298          * get back the whole principal as-sent 
299          *
300          * Finally, if we are not set to canonicalize, we get back the
301          * fixed UPPER case realm, but the as-sent username
302          */
303         if (test_data->canonicalize) {
304                 expected_principal_string = talloc_asprintf(test_data, "%s@%s", test_data->username, upper_real_realm);
305         } else if (test_data->enterprise) {
306                 expected_principal_string = principal_string;
307         } else {
308                 expected_principal_string = talloc_asprintf(test_data, "%s@%s", username, upper_real_realm);
309         }
310         
311         if (test_data->enterprise) {
312                 principal_flags = KRB5_PRINCIPAL_PARSE_ENTERPRISE;
313         } else {
314                 principal_flags = 0;
315         }
316
317         if (test_data->canonicalize) {
318                 expected_principal_flags = 0;
319         } else {
320                 expected_principal_flags = principal_flags;
321         }
322
323         torture_assert_int_equal(tctx,
324                                  krb5_parse_name_flags(smb_krb5_context->krb5_context,
325                                                        principal_string,
326                                                        principal_flags,
327                                                        &principal),
328                                          0, "krb5_parse_name_flags failed");
329         torture_assert_int_equal(tctx,
330                                  krb5_parse_name_flags(smb_krb5_context->krb5_context,
331                                                        expected_principal_string,
332                                                        expected_principal_flags,
333                                                        &expected_principal),
334                                  0, "krb5_parse_name_flags failed");
335
336         /* 
337          * Set the canonicalize flag if this test requires it
338          */
339         torture_assert_int_equal(tctx,
340                                  krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options),
341                                  0, "krb5_get_init_creds_opt_alloc failed");
342                 
343         torture_assert_int_equal(tctx,
344                                  krb5_get_init_creds_opt_set_canonicalize(smb_krb5_context->krb5_context, krb_options, test_data->canonicalize),
345                                  0, "krb5_get_init_creds_opt_set_canonicalize failed");
346
347         k5ret = krb5_get_init_creds_password(smb_krb5_context->krb5_context, &my_creds, principal,
348                                              password, NULL, NULL, 0,
349                                              NULL, krb_options);
350         krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options);
351         
352         assertion_message = talloc_asprintf(tctx,
353                                             "krb5_get_init_creds_password for %s failed: %s",
354                                             principal_string,
355                                             smb_get_krb5_error_message(smb_krb5_context->krb5_context, k5ret, tctx));
356         torture_assert_int_equal(tctx, k5ret, 0, assertion_message);
357
358         /*
359          * Assert that the reply was with the correct type of
360          * principal, depending on the flags we set
361          */
362         if (test_data->canonicalize == false && test_data->enterprise) {
363                 torture_assert_int_equal(tctx,
364                                          krb5_principal_get_type(smb_krb5_context->krb5_context,
365                                                                  my_creds.client), KRB5_NT_ENTERPRISE_PRINCIPAL,
366                                          "smb_krb5_init_context gave incorrect client->name.name_type");
367         } else {
368                 torture_assert_int_equal(tctx,
369                                          krb5_principal_get_type(smb_krb5_context->krb5_context,
370                                                                  my_creds.client), KRB5_NT_PRINCIPAL,
371                                          "smb_krb5_init_context gave incorrect client->name.name_type");
372         }
373         
374         torture_assert_int_equal(tctx,
375                                  krb5_unparse_name(smb_krb5_context->krb5_context,
376                                                    my_creds.client, &got_principal_string), 0,
377                                  "krb5_unparse_name failed");
378
379         assertion_message = talloc_asprintf(tctx,
380                                             "krb5_get_init_creds_password returned a different principal %s to what was expected %s",
381                                             got_principal_string, expected_principal_string);
382         krb5_free_unparsed_name(smb_krb5_context->krb5_context, got_principal_string);
383         
384         torture_assert(tctx, krb5_principal_compare(smb_krb5_context->krb5_context,
385                                                     my_creds.client, expected_principal),
386                        assertion_message);
387         
388         torture_assert_int_equal(tctx,
389                                  krb5_principal_get_type(smb_krb5_context->krb5_context,
390                                                          my_creds.server), KRB5_NT_SRV_INST,
391                                  "smb_krb5_init_context gave incorrect client->name.name_type");
392
393         torture_assert_str_equal(tctx, krb5_principal_get_comp_string(smb_krb5_context->krb5_context,
394                                                                       my_creds.server, 0),
395                                  "krbtgt",
396                                  "smb_krb5_init_context gave incorrect my_creds.server->name.name_string[0]");
397
398         krb5_free_principal(smb_krb5_context->krb5_context, principal);
399         
400         k5ret = krb5_free_cred_contents(smb_krb5_context->krb5_context, &my_creds);
401         torture_assert_int_equal(tctx, k5ret, 0, "krb5_free_creds failed");
402
403         return true;
404 }
405
406 struct torture_suite *torture_krb5_canon(TALLOC_CTX *mem_ctx)
407 {
408         unsigned int i;
409         struct torture_suite *suite = torture_suite_create(mem_ctx, "canon");
410         suite->description = talloc_strdup(suite, "Kerberos Canonicalisation tests");
411
412         for (i = 0; i < TEST_ALL; i++) {
413                 char *name = talloc_asprintf(suite, "%s.%s.%s.%s.%s",
414                                              (i & TEST_CANONICALIZE) ? "canon" : "no-canon",
415                                              (i & TEST_ENTERPRISE) ? "enterprise" : "no-enterprise",
416                                              (i & TEST_UPPER_REALM) ? "uc-realm" : "lc-realm",
417                                              (i & TEST_UPPER_USERNAME) ? "uc-user" : "lc-user",
418                                              (i & TEST_NETBIOS_REALM) ? "netbios-realm" : "krb5-realm");
419
420                 struct test_data *test_data = talloc(suite, struct test_data);
421                 if (i & TEST_NETBIOS_REALM) {
422                         test_data->realm = cli_credentials_get_domain(cmdline_credentials);
423                 } else {
424                         test_data->realm = cli_credentials_get_realm(cmdline_credentials);
425                 }
426                 test_data->real_realm = cli_credentials_get_realm(cmdline_credentials);
427                 test_data->username = cli_credentials_get_username(cmdline_credentials);
428                 test_data->canonicalize = (i & TEST_CANONICALIZE) != 0;
429                 test_data->enterprise = (i & TEST_ENTERPRISE) != 0;
430                 test_data->upper_realm = (i & TEST_UPPER_REALM) != 0;
431                 test_data->upper_username = (i & TEST_UPPER_USERNAME) != 0;
432                 torture_suite_add_simple_tcase_const(suite, name, torture_krb5_as_req_canon,
433                                                      test_data);
434                                                      
435         }
436         return suite;
437 }