Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Kerberos utility functions for GENSEC
5 :
6 : Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
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 "auth/kerberos/kerberos.h"
26 : #include "auth/credentials/credentials.h"
27 : #include "auth/credentials/credentials_krb5.h"
28 : #include "auth/kerberos/kerberos_credentials.h"
29 : #include "auth/kerberos/kerberos_util.h"
30 :
31 : struct principal_container {
32 : struct smb_krb5_context *smb_krb5_context;
33 : krb5_principal principal;
34 : const char *string_form; /* Optional */
35 : };
36 :
37 40108 : static krb5_error_code free_principal(struct principal_container *pc)
38 : {
39 : /* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */
40 40108 : krb5_free_principal(pc->smb_krb5_context->krb5_context, pc->principal);
41 :
42 40108 : return 0;
43 : }
44 :
45 :
46 47885 : static krb5_error_code parse_principal(TALLOC_CTX *parent_ctx,
47 : const char *princ_string,
48 : struct smb_krb5_context *smb_krb5_context,
49 : krb5_principal *princ,
50 : const char **error_string)
51 : {
52 : int ret;
53 : struct principal_container *mem_ctx;
54 47885 : if (princ_string == NULL) {
55 7777 : *princ = NULL;
56 7777 : return 0;
57 : }
58 :
59 40108 : ret = krb5_parse_name(smb_krb5_context->krb5_context,
60 : princ_string, princ);
61 :
62 40108 : if (ret) {
63 0 : (*error_string) = smb_get_krb5_error_message(
64 : smb_krb5_context->krb5_context,
65 : ret, parent_ctx);
66 0 : return ret;
67 : }
68 :
69 40108 : mem_ctx = talloc(parent_ctx, struct principal_container);
70 40108 : if (!mem_ctx) {
71 0 : (*error_string) = error_message(ENOMEM);
72 0 : return ENOMEM;
73 : }
74 :
75 : /* This song-and-dance effectivly puts the principal
76 : * into talloc, so we can't loose it. */
77 40108 : mem_ctx->smb_krb5_context = talloc_reference(mem_ctx,
78 : smb_krb5_context);
79 40108 : mem_ctx->principal = *princ;
80 40108 : talloc_set_destructor(mem_ctx, free_principal);
81 40108 : return 0;
82 : }
83 :
84 : /* Obtain the principal set on this context. Requires a
85 : * smb_krb5_context because we are doing krb5 principal parsing with
86 : * the library routines. The returned princ is placed in the talloc
87 : * system by means of a destructor (do *not* free). */
88 :
89 40085 : krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
90 : struct cli_credentials *credentials,
91 : struct smb_krb5_context *smb_krb5_context,
92 : krb5_principal *princ,
93 : enum credentials_obtained *obtained,
94 : const char **error_string)
95 : {
96 : krb5_error_code ret;
97 : const char *princ_string;
98 40085 : TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
99 40085 : *obtained = CRED_UNINITIALISED;
100 :
101 40085 : if (!mem_ctx) {
102 0 : (*error_string) = error_message(ENOMEM);
103 0 : return ENOMEM;
104 : }
105 40085 : princ_string = cli_credentials_get_principal_and_obtained(credentials,
106 : mem_ctx,
107 : obtained);
108 40085 : if (!princ_string) {
109 6 : *princ = NULL;
110 6 : return 0;
111 : }
112 :
113 40079 : ret = parse_principal(parent_ctx, princ_string,
114 : smb_krb5_context, princ, error_string);
115 40079 : talloc_free(mem_ctx);
116 40079 : return ret;
117 : }
118 :
119 : /* Obtain the principal set on this context. Requires a
120 : * smb_krb5_context because we are doing krb5 principal parsing with
121 : * the library routines. The returned princ is placed in the talloc
122 : * system by means of a destructor (do *not* free). */
123 :
124 7806 : static krb5_error_code impersonate_principal_from_credentials(
125 : TALLOC_CTX *parent_ctx,
126 : struct cli_credentials *credentials,
127 : struct smb_krb5_context *smb_krb5_context,
128 : krb5_principal *princ,
129 : const char **error_string)
130 : {
131 7806 : return parse_principal(parent_ctx,
132 : cli_credentials_get_impersonate_principal(credentials),
133 : smb_krb5_context, princ, error_string);
134 : }
135 :
136 241 : krb5_error_code smb_krb5_create_principals_array(TALLOC_CTX *mem_ctx,
137 : krb5_context context,
138 : const char *account_name,
139 : const char *realm,
140 : uint32_t num_spns,
141 : const char *spns[],
142 : uint32_t *pnum_principals,
143 : krb5_principal **pprincipals,
144 : const char **error_string)
145 : {
146 : krb5_error_code code;
147 : TALLOC_CTX *tmp_ctx;
148 241 : uint32_t num_principals = 0;
149 : krb5_principal *principals;
150 : uint32_t i;
151 :
152 241 : tmp_ctx = talloc_new(mem_ctx);
153 241 : if (tmp_ctx == NULL) {
154 0 : *error_string = "Cannot allocate tmp_ctx";
155 0 : return ENOMEM;
156 : }
157 :
158 241 : if (realm == NULL) {
159 0 : *error_string = "Cannot create principal without a realm";
160 0 : code = EINVAL;
161 0 : goto done;
162 : }
163 :
164 241 : if (account_name == NULL && (num_spns == 0 || spns == NULL)) {
165 0 : *error_string = "Cannot create principal without an account or SPN";
166 0 : code = EINVAL;
167 0 : goto done;
168 : }
169 :
170 241 : if (account_name != NULL && account_name[0] != '\0') {
171 241 : num_principals++;
172 : }
173 241 : num_principals += num_spns;
174 :
175 241 : principals = talloc_zero_array(tmp_ctx,
176 : krb5_principal,
177 : num_principals);
178 241 : if (principals == NULL) {
179 0 : *error_string = "Cannot allocate principals";
180 0 : code = ENOMEM;
181 0 : goto done;
182 : }
183 :
184 570 : for (i = 0; i < num_spns; i++) {
185 329 : code = krb5_parse_name(context, spns[i], &(principals[i]));
186 329 : if (code != 0) {
187 0 : *error_string = smb_get_krb5_error_message(context,
188 : code,
189 : mem_ctx);
190 0 : goto done;
191 : }
192 : }
193 :
194 241 : if (account_name != NULL && account_name[0] != '\0') {
195 241 : code = smb_krb5_make_principal(context,
196 241 : &(principals[i]),
197 : realm,
198 : account_name,
199 : NULL);
200 241 : if (code != 0) {
201 0 : *error_string = smb_get_krb5_error_message(context,
202 : code,
203 : mem_ctx);
204 0 : goto done;
205 : }
206 : }
207 :
208 241 : if (pnum_principals != NULL) {
209 241 : *pnum_principals = num_principals;
210 :
211 241 : if (pprincipals != NULL) {
212 241 : *pprincipals = talloc_steal(mem_ctx, principals);
213 : }
214 : }
215 :
216 241 : code = 0;
217 241 : done:
218 241 : talloc_free(tmp_ctx);
219 241 : return code;
220 : }
221 :
222 : /**
223 : * Return a freshly allocated ccache (destroyed by destructor on child
224 : * of parent_ctx), for a given set of client credentials
225 : */
226 :
227 7808 : krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx,
228 : struct cli_credentials *credentials,
229 : struct smb_krb5_context *smb_krb5_context,
230 : struct tevent_context *event_ctx,
231 : krb5_ccache ccache,
232 : enum credentials_obtained *obtained,
233 : const char **error_string)
234 : {
235 : krb5_error_code ret;
236 : const char *password;
237 : const char *self_service;
238 : const char *target_service;
239 7808 : time_t kdc_time = 0;
240 : krb5_principal princ;
241 : krb5_principal impersonate_principal;
242 : int tries;
243 7808 : TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
244 : krb5_get_init_creds_opt *krb_options;
245 :
246 7808 : if (!mem_ctx) {
247 0 : (*error_string) = strerror(ENOMEM);
248 0 : return ENOMEM;
249 : }
250 :
251 7808 : ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ, obtained, error_string);
252 7808 : if (ret) {
253 0 : talloc_free(mem_ctx);
254 0 : return ret;
255 : }
256 :
257 7808 : if (princ == NULL) {
258 2 : (*error_string) = talloc_asprintf(credentials, "principal, username or realm was not specified in the credentials");
259 2 : talloc_free(mem_ctx);
260 2 : return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
261 : }
262 :
263 7806 : ret = impersonate_principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &impersonate_principal, error_string);
264 7806 : if (ret) {
265 0 : talloc_free(mem_ctx);
266 0 : return ret;
267 : }
268 :
269 7806 : self_service = cli_credentials_get_self_service(credentials);
270 7806 : target_service = cli_credentials_get_target_service(credentials);
271 :
272 7806 : password = cli_credentials_get_password(credentials);
273 :
274 : /* setup the krb5 options we want */
275 7806 : if ((ret = krb5_get_init_creds_opt_alloc(smb_krb5_context->krb5_context, &krb_options))) {
276 0 : (*error_string) = talloc_asprintf(credentials, "krb5_get_init_creds_opt_alloc failed (%s)\n",
277 : smb_get_krb5_error_message(smb_krb5_context->krb5_context,
278 : ret, mem_ctx));
279 0 : talloc_free(mem_ctx);
280 0 : return ret;
281 : }
282 :
283 : #ifdef SAMBA4_USES_HEIMDAL /* Disable for now MIT reads defaults when needed */
284 : /* get the defaults */
285 6955 : krb5_get_init_creds_opt_set_default_flags(smb_krb5_context->krb5_context, NULL, NULL, krb_options);
286 : #endif
287 : /* set if we want a forwardable ticket */
288 7806 : switch (cli_credentials_get_krb_forwardable(credentials)) {
289 7790 : case CRED_AUTO_KRB_FORWARDABLE:
290 7790 : break;
291 16 : case CRED_NO_KRB_FORWARDABLE:
292 16 : krb5_get_init_creds_opt_set_forwardable(krb_options, FALSE);
293 16 : break;
294 0 : case CRED_FORCE_KRB_FORWARDABLE:
295 0 : krb5_get_init_creds_opt_set_forwardable(krb_options, TRUE);
296 0 : break;
297 : }
298 :
299 : #ifdef SAMBA4_USES_HEIMDAL /* FIXME: MIT does not have this yet */
300 : /*
301 : * In order to work against windows KDCs even if we use
302 : * the netbios domain name as realm, we need to add the following
303 : * flags:
304 : * KRB5_INIT_CREDS_NO_C_CANON_CHECK;
305 : * KRB5_INIT_CREDS_NO_C_NO_EKU_CHECK;
306 : *
307 : * On MIT: Set pkinit_eku_checking to none
308 : */
309 6955 : krb5_get_init_creds_opt_set_win2k(smb_krb5_context->krb5_context,
310 : krb_options, true);
311 6955 : krb5_get_init_creds_opt_set_canonicalize(smb_krb5_context->krb5_context,
312 : krb_options, true);
313 : #else /* MIT */
314 851 : krb5_get_init_creds_opt_set_canonicalize(krb_options, true);
315 : #endif
316 :
317 7806 : tries = 2;
318 14761 : while (tries--) {
319 : #ifdef SAMBA4_USES_HEIMDAL
320 : struct tevent_context *previous_ev;
321 : /* Do this every time, in case we have weird recursive issues here */
322 6955 : ret = smb_krb5_context_set_event_ctx(smb_krb5_context, event_ctx, &previous_ev);
323 6955 : if (ret) {
324 0 : talloc_free(mem_ctx);
325 1 : return ret;
326 : }
327 : #endif
328 7806 : if (password) {
329 7798 : if (impersonate_principal) {
330 29 : ret = smb_krb5_kinit_s4u2_ccache(smb_krb5_context->krb5_context,
331 : ccache,
332 : princ,
333 : password,
334 : impersonate_principal,
335 : self_service,
336 : target_service,
337 : krb_options,
338 : NULL,
339 : &kdc_time);
340 : } else {
341 7769 : ret = smb_krb5_kinit_password_ccache(smb_krb5_context->krb5_context,
342 : ccache,
343 : princ,
344 : password,
345 : target_service,
346 : krb_options,
347 : NULL,
348 : &kdc_time);
349 : }
350 8 : } else if (impersonate_principal) {
351 0 : talloc_free(mem_ctx);
352 0 : (*error_string) = "INTERNAL error: Cannot impersonate principal with just a keyblock. A password must be specified in the credentials";
353 0 : return EINVAL;
354 : } else {
355 : /* No password available, try to use a keyblock instead */
356 :
357 : krb5_keyblock keyblock;
358 : const struct samr_Password *mach_pwd;
359 8 : mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx);
360 8 : if (!mach_pwd) {
361 2 : talloc_free(mem_ctx);
362 2 : (*error_string) = "kinit_to_ccache: No password available for kinit\n";
363 2 : krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options);
364 : #ifdef SAMBA4_USES_HEIMDAL
365 1 : smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx);
366 : #endif
367 2 : return EINVAL;
368 : }
369 6 : ret = smb_krb5_keyblock_init_contents(smb_krb5_context->krb5_context,
370 : ENCTYPE_ARCFOUR_HMAC,
371 6 : mach_pwd->hash, sizeof(mach_pwd->hash),
372 : &keyblock);
373 :
374 6 : if (ret == 0) {
375 6 : ret = smb_krb5_kinit_keyblock_ccache(smb_krb5_context->krb5_context,
376 : ccache,
377 : princ,
378 : &keyblock,
379 : target_service,
380 : krb_options,
381 : NULL,
382 : &kdc_time);
383 6 : krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock);
384 : }
385 : }
386 :
387 : #ifdef SAMBA4_USES_HEIMDAL
388 6954 : smb_krb5_context_remove_event_ctx(smb_krb5_context, previous_ev, event_ctx);
389 : #endif
390 :
391 7804 : if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
392 : /* Perhaps we have been given an invalid skew, so try again without it */
393 0 : time_t t = time(NULL);
394 0 : krb5_set_real_time(smb_krb5_context->krb5_context, t, 0);
395 : } else {
396 : /* not a skew problem */
397 : break;
398 : }
399 : }
400 :
401 7804 : krb5_get_init_creds_opt_free(smb_krb5_context->krb5_context, krb_options);
402 :
403 7804 : if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) {
404 0 : (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n",
405 : cli_credentials_get_principal(credentials, mem_ctx),
406 : smb_get_krb5_error_message(smb_krb5_context->krb5_context,
407 : ret, mem_ctx));
408 0 : talloc_free(mem_ctx);
409 0 : return ret;
410 : }
411 :
412 : /* cope with ticket being in the future due to clock skew */
413 7804 : if ((unsigned)kdc_time > time(NULL)) {
414 0 : time_t t = time(NULL);
415 0 : int time_offset =(unsigned)kdc_time-t;
416 0 : DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset));
417 0 : krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0);
418 : }
419 :
420 7804 : if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) {
421 0 : ret = kinit_to_ccache(parent_ctx,
422 : credentials,
423 : smb_krb5_context,
424 : event_ctx,
425 : ccache, obtained,
426 : error_string);
427 : }
428 :
429 7804 : if (ret) {
430 381 : (*error_string) = talloc_asprintf(credentials, "kinit for %s failed (%s)\n",
431 : cli_credentials_get_principal(credentials, mem_ctx),
432 : smb_get_krb5_error_message(smb_krb5_context->krb5_context,
433 : ret, mem_ctx));
434 381 : talloc_free(mem_ctx);
435 381 : return ret;
436 : }
437 :
438 7423 : DEBUG(10,("kinit for %s succeeded\n",
439 : cli_credentials_get_principal(credentials, mem_ctx)));
440 :
441 :
442 7423 : talloc_free(mem_ctx);
443 7423 : return 0;
444 : }
445 :
446 41053 : static krb5_error_code free_keytab_container(struct keytab_container *ktc)
447 : {
448 41053 : return krb5_kt_close(ktc->smb_krb5_context->krb5_context, ktc->keytab);
449 : }
450 :
451 41000 : krb5_error_code smb_krb5_get_keytab_container(TALLOC_CTX *mem_ctx,
452 : struct smb_krb5_context *smb_krb5_context,
453 : krb5_keytab opt_keytab,
454 : const char *keytab_name,
455 : struct keytab_container **ktc)
456 : {
457 : krb5_keytab keytab;
458 : krb5_error_code ret;
459 :
460 41000 : if (opt_keytab) {
461 67 : keytab = opt_keytab;
462 : } else {
463 40933 : ret = krb5_kt_resolve(smb_krb5_context->krb5_context,
464 : keytab_name, &keytab);
465 40933 : if (ret) {
466 0 : DEBUG(1,("failed to open krb5 keytab: %s\n",
467 : smb_get_krb5_error_message(
468 : smb_krb5_context->krb5_context,
469 : ret, mem_ctx)));
470 0 : return ret;
471 : }
472 : }
473 :
474 41000 : *ktc = talloc(mem_ctx, struct keytab_container);
475 41000 : if (!*ktc) {
476 0 : return ENOMEM;
477 : }
478 :
479 41000 : (*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context);
480 41000 : (*ktc)->keytab = keytab;
481 41000 : (*ktc)->password_based = false;
482 41000 : talloc_set_destructor(*ktc, free_keytab_container);
483 :
484 41000 : return 0;
485 : }
486 :
487 : /*
488 : * Walk the keytab, looking for entries of this principal name,
489 : * with KVNO other than current kvno -1.
490 : *
491 : * These entries are now stale,
492 : * we only keep the current and previous entries around.
493 : *
494 : * Inspired by the code in Samba3 for 'use kerberos keytab'.
495 : */
496 241 : krb5_error_code smb_krb5_remove_obsolete_keytab_entries(TALLOC_CTX *mem_ctx,
497 : krb5_context context,
498 : krb5_keytab keytab,
499 : uint32_t num_principals,
500 : krb5_principal *principals,
501 : krb5_kvno kvno,
502 : bool *found_previous,
503 : const char **error_string)
504 : {
505 : TALLOC_CTX *tmp_ctx;
506 : krb5_error_code code;
507 : krb5_kt_cursor cursor;
508 :
509 241 : tmp_ctx = talloc_new(mem_ctx);
510 241 : if (tmp_ctx == NULL) {
511 0 : *error_string = "Cannot allocate tmp_ctx";
512 0 : return ENOMEM;
513 : }
514 :
515 241 : *found_previous = true;
516 :
517 241 : code = krb5_kt_start_seq_get(context, keytab, &cursor);
518 241 : switch (code) {
519 107 : case 0:
520 107 : break;
521 : #ifdef HEIM_ERR_OPNOTSUPP
522 : case HEIM_ERR_OPNOTSUPP:
523 : #endif
524 134 : case ENOENT:
525 : case KRB5_KT_END:
526 : /* no point enumerating if there isn't anything here */
527 134 : code = 0;
528 134 : goto done;
529 0 : default:
530 0 : *error_string = talloc_asprintf(mem_ctx,
531 : "failed to open keytab for read of old entries: %s\n",
532 : smb_get_krb5_error_message(context, code, mem_ctx));
533 0 : goto done;
534 : }
535 :
536 1452 : do {
537 1559 : krb5_kvno old_kvno = kvno - 1;
538 : krb5_keytab_entry entry;
539 1559 : bool matched = false;
540 : uint32_t i;
541 :
542 1559 : code = krb5_kt_next_entry(context, keytab, &entry, &cursor);
543 1559 : if (code) {
544 195 : break;
545 : }
546 :
547 3227 : for (i = 0; i < num_principals; i++) {
548 : krb5_boolean ok;
549 :
550 2978 : ok = smb_krb5_kt_compare(context,
551 : &entry,
552 2978 : principals[i],
553 : 0,
554 : 0);
555 2978 : if (ok) {
556 1203 : matched = true;
557 1203 : break;
558 : }
559 : }
560 :
561 1452 : if (!matched) {
562 : /*
563 : * Free the entry, it wasn't the one we were looking
564 : * for anyway
565 : */
566 249 : krb5_kt_free_entry(context, &entry);
567 : /* Make sure we do not double free */
568 249 : ZERO_STRUCT(entry);
569 249 : continue;
570 : }
571 :
572 : /*
573 : * Delete it, if it is not kvno - 1.
574 : *
575 : * Some keytab files store the kvno only in 8bits. Limit the
576 : * compare to 8bits, so that we don't miss old keys and delete
577 : * them.
578 : */
579 1203 : if ((entry.vno & 0xff) != (old_kvno & 0xff)) {
580 : krb5_error_code rc;
581 :
582 : /* Release the enumeration. We are going to
583 : * have to start this from the top again,
584 : * because deletes during enumeration may not
585 : * always be consistent.
586 : *
587 : * Also, the enumeration locks a FILE: keytab
588 : */
589 230 : krb5_kt_end_seq_get(context, keytab, &cursor);
590 :
591 230 : code = krb5_kt_remove_entry(context, keytab, &entry);
592 230 : krb5_kt_free_entry(context, &entry);
593 :
594 : /* Make sure we do not double free */
595 230 : ZERO_STRUCT(entry);
596 :
597 : /* Deleted: Restart from the top */
598 230 : rc = krb5_kt_start_seq_get(context, keytab, &cursor);
599 230 : if (rc != 0) {
600 0 : krb5_kt_free_entry(context, &entry);
601 :
602 : /* Make sure we do not double free */
603 0 : ZERO_STRUCT(entry);
604 :
605 0 : DEBUG(1, ("failed to restart enumeration of keytab: %s\n",
606 : smb_get_krb5_error_message(context,
607 : code,
608 : tmp_ctx)));
609 :
610 0 : talloc_free(tmp_ctx);
611 0 : return rc;
612 : }
613 :
614 230 : if (code != 0) {
615 0 : break;
616 : }
617 :
618 : } else {
619 973 : *found_previous = true;
620 : }
621 :
622 : /* Free the entry, we don't need it any more */
623 1203 : krb5_kt_free_entry(context, &entry);
624 : /* Make sure we do not double free */
625 1203 : ZERO_STRUCT(entry);
626 1452 : } while (code == 0);
627 :
628 107 : krb5_kt_end_seq_get(context, keytab, &cursor);
629 :
630 107 : switch (code) {
631 0 : case 0:
632 0 : break;
633 107 : case ENOENT:
634 : case KRB5_KT_END:
635 107 : break;
636 0 : default:
637 0 : *error_string = talloc_asprintf(mem_ctx,
638 : "failed in deleting old entries for principal: %s\n",
639 : smb_get_krb5_error_message(context,
640 : code,
641 : mem_ctx));
642 : }
643 :
644 107 : code = 0;
645 241 : done:
646 241 : talloc_free(tmp_ctx);
647 241 : return code;
648 : }
|