1/* 2 * Copyright (c) 2004 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#define HAVE_TSASL 1 35 36#include "kadm5_locl.h" 37#if 1 38#undef OPENLDAP 39#undef HAVE_TSASL 40#endif 41#ifdef OPENLDAP 42#include <ldap.h> 43#ifdef HAVE_TSASL 44#include <tsasl.h> 45#endif 46#include <resolve.h> 47#include <base64.h> 48#endif 49 50RCSID("$Id$"); 51 52#ifdef OPENLDAP 53 54#define CTX2LP(context) ((LDAP *)((context)->ldap_conn)) 55#define CTX2BASE(context) ((context)->base_dn) 56 57/* 58 * userAccountControl 59 */ 60 61#define UF_SCRIPT 0x00000001 62#define UF_ACCOUNTDISABLE 0x00000002 63#define UF_UNUSED_0 0x00000004 64#define UF_HOMEDIR_REQUIRED 0x00000008 65#define UF_LOCKOUT 0x00000010 66#define UF_PASSWD_NOTREQD 0x00000020 67#define UF_PASSWD_CANT_CHANGE 0x00000040 68#define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x00000080 69#define UF_TEMP_DUPLICATE_ACCOUNT 0x00000100 70#define UF_NORMAL_ACCOUNT 0x00000200 71#define UF_UNUSED_1 0x00000400 72#define UF_INTERDOMAIN_TRUST_ACCOUNT 0x00000800 73#define UF_WORKSTATION_TRUST_ACCOUNT 0x00001000 74#define UF_SERVER_TRUST_ACCOUNT 0x00002000 75#define UF_UNUSED_2 0x00004000 76#define UF_UNUSED_3 0x00008000 77#define UF_PASSWD_NOT_EXPIRE 0x00010000 78#define UF_MNS_LOGON_ACCOUNT 0x00020000 79#define UF_SMARTCARD_REQUIRED 0x00040000 80#define UF_TRUSTED_FOR_DELEGATION 0x00080000 81#define UF_NOT_DELEGATED 0x00100000 82#define UF_USE_DES_KEY_ONLY 0x00200000 83#define UF_DONT_REQUIRE_PREAUTH 0x00400000 84#define UF_UNUSED_4 0x00800000 85#define UF_UNUSED_5 0x01000000 86#define UF_UNUSED_6 0x02000000 87#define UF_UNUSED_7 0x04000000 88#define UF_UNUSED_8 0x08000000 89#define UF_UNUSED_9 0x10000000 90#define UF_UNUSED_10 0x20000000 91#define UF_UNUSED_11 0x40000000 92#define UF_UNUSED_12 0x80000000 93 94/* 95 * 96 */ 97 98#ifndef HAVE_TSASL 99static int 100sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *interact) 101{ 102 return LDAP_SUCCESS; 103} 104#endif 105 106#if 0 107static Sockbuf_IO ldap_tsasl_io = { 108 NULL, /* sbi_setup */ 109 NULL, /* sbi_remove */ 110 NULL, /* sbi_ctrl */ 111 NULL, /* sbi_read */ 112 NULL, /* sbi_write */ 113 NULL /* sbi_close */ 114}; 115#endif 116 117#ifdef HAVE_TSASL 118static int 119ldap_tsasl_bind_s(LDAP *ld, 120 LDAP_CONST char *dn, 121 LDAPControl **serverControls, 122 LDAPControl **clientControls, 123 const char *host) 124{ 125 char *attrs[] = { "supportedSASLMechanisms", NULL }; 126 struct tsasl_peer *peer = NULL; 127 struct tsasl_buffer in, out; 128 struct berval ccred, *scred; 129 LDAPMessage *m, *m0; 130 const char *mech; 131 char **vals; 132 int ret, rc; 133 134 ret = tsasl_peer_init(TSASL_FLAGS_INITIATOR | TSASL_FLAGS_CLEAR, 135 "ldap", host, &peer); 136 if (ret != TSASL_DONE) { 137 rc = LDAP_LOCAL_ERROR; 138 goto out; 139 } 140 141 rc = ldap_search_s(ld, "", LDAP_SCOPE_BASE, NULL, attrs, 0, &m0); 142 if (rc != LDAP_SUCCESS) 143 goto out; 144 145 m = ldap_first_entry(ld, m0); 146 if (m == NULL) { 147 ldap_msgfree(m0); 148 goto out; 149 } 150 151 vals = ldap_get_values(ld, m, "supportedSASLMechanisms"); 152 if (vals == NULL) { 153 ldap_msgfree(m0); 154 goto out; 155 } 156 157 ret = tsasl_find_best_mech(peer, vals, &mech); 158 if (ret) { 159 ldap_msgfree(m0); 160 goto out; 161 } 162 163 ldap_msgfree(m0); 164 165 ret = tsasl_select_mech(peer, mech); 166 if (ret != TSASL_DONE) { 167 rc = LDAP_LOCAL_ERROR; 168 goto out; 169 } 170 171 in.tb_data = NULL; 172 in.tb_size = 0; 173 174 do { 175 ret = tsasl_request(peer, &in, &out); 176 if (in.tb_size != 0) { 177 free(in.tb_data); 178 in.tb_data = NULL; 179 in.tb_size = 0; 180 } 181 if (ret != TSASL_DONE && ret != TSASL_CONTINUE) { 182 rc = LDAP_AUTH_UNKNOWN; 183 goto out; 184 } 185 186 ccred.bv_val = out.tb_data; 187 ccred.bv_len = out.tb_size; 188 189 rc = ldap_sasl_bind_s(ld, dn, mech, &ccred, 190 serverControls, clientControls, &scred); 191 tsasl_buffer_free(&out); 192 193 if (rc != LDAP_SUCCESS && rc != LDAP_SASL_BIND_IN_PROGRESS) { 194 if(scred && scred->bv_len) 195 ber_bvfree(scred); 196 goto out; 197 } 198 199 in.tb_data = malloc(scred->bv_len); 200 if (in.tb_data == NULL) { 201 rc = LDAP_LOCAL_ERROR; 202 goto out; 203 } 204 memcpy(in.tb_data, scred->bv_val, scred->bv_len); 205 in.tb_size = scred->bv_len; 206 ber_bvfree(scred); 207 208 } while (rc == LDAP_SASL_BIND_IN_PROGRESS); 209 210 out: 211 if (rc == LDAP_SUCCESS) { 212#if 0 213 ber_sockbuf_add_io(ld->ld_conns->lconn_sb, &ldap_tsasl_io, 214 LBER_SBIOD_LEVEL_APPLICATION, peer); 215 216#endif 217 } else if (peer != NULL) 218 tsasl_peer_free(peer); 219 220 return rc; 221} 222#endif /* HAVE_TSASL */ 223 224 225static int 226check_ldap(kadm5_ad_context *context, int ret) 227{ 228 switch (ret) { 229 case LDAP_SUCCESS: 230 return 0; 231 case LDAP_SERVER_DOWN: { 232 LDAP *lp = CTX2LP(context); 233 ldap_unbind(lp); 234 context->ldap_conn = NULL; 235 free(context->base_dn); 236 context->base_dn = NULL; 237 return 1; 238 } 239 default: 240 return 1; 241 } 242} 243 244/* 245 * 246 */ 247 248static void 249laddattr(char ***al, int *attrlen, char *attr) 250{ 251 char **a; 252 a = realloc(*al, (*attrlen + 2) * sizeof(**al)); 253 if (a == NULL) 254 return; 255 a[*attrlen] = attr; 256 a[*attrlen + 1] = NULL; 257 (*attrlen)++; 258 *al = a; 259} 260 261static kadm5_ret_t 262_kadm5_ad_connect(void *server_handle) 263{ 264 kadm5_ad_context *context = server_handle; 265 struct { 266 char *server; 267 int port; 268 } *s, *servers = NULL; 269 int i, num_servers = 0; 270 271 if (context->ldap_conn) 272 return 0; 273 274 { 275 struct dns_reply *r; 276 struct resource_record *rr; 277 char *domain; 278 279 asprintf(&domain, "_ldap._tcp.%s", context->realm); 280 if (domain == NULL) { 281 krb5_set_error_message(context->context, KADM5_NO_SRV, "malloc"); 282 return KADM5_NO_SRV; 283 } 284 285 r = dns_lookup(domain, "SRV"); 286 free(domain); 287 if (r == NULL) { 288 krb5_set_error_message(context->context, KADM5_NO_SRV, "Didn't find ldap dns"); 289 return KADM5_NO_SRV; 290 } 291 292 for (rr = r->head ; rr != NULL; rr = rr->next) { 293 if (rr->type != rk_ns_t_srv) 294 continue; 295 s = realloc(servers, sizeof(*servers) * (num_servers + 1)); 296 if (s == NULL) { 297 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "malloc"); 298 dns_free_data(r); 299 goto fail; 300 } 301 servers = s; 302 num_servers++; 303 servers[num_servers - 1].port = rr->u.srv->port; 304 servers[num_servers - 1].server = strdup(rr->u.srv->target); 305 } 306 dns_free_data(r); 307 } 308 309 if (num_servers == 0) { 310 krb5_set_error_message(context->context, KADM5_NO_SRV, "No AD server found in DNS"); 311 return KADM5_NO_SRV; 312 } 313 314 for (i = 0; i < num_servers; i++) { 315 int lret, version = LDAP_VERSION3; 316 LDAP *lp; 317 318 lp = ldap_init(servers[i].server, servers[i].port); 319 if (lp == NULL) 320 continue; 321 322 if (ldap_set_option(lp, LDAP_OPT_PROTOCOL_VERSION, &version)) { 323 ldap_unbind(lp); 324 continue; 325 } 326 327 if (ldap_set_option(lp, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)) { 328 ldap_unbind(lp); 329 continue; 330 } 331 332#ifdef HAVE_TSASL 333 lret = ldap_tsasl_bind_s(lp, NULL, NULL, NULL, servers[i].server); 334 335#else 336 lret = ldap_sasl_interactive_bind_s(lp, NULL, NULL, NULL, NULL, 337 LDAP_SASL_QUIET, 338 sasl_interact, NULL); 339#endif 340 if (lret != LDAP_SUCCESS) { 341 krb5_set_error_message(context->context, 0, 342 "Couldn't contact any AD servers: %s", 343 ldap_err2string(lret)); 344 ldap_unbind(lp); 345 continue; 346 } 347 348 context->ldap_conn = lp; 349 break; 350 } 351 if (i >= num_servers) { 352 goto fail; 353 } 354 355 { 356 LDAPMessage *m, *m0; 357 char **attr = NULL; 358 int attrlen = 0; 359 char **vals; 360 int ret; 361 362 laddattr(&attr, &attrlen, "defaultNamingContext"); 363 364 ret = ldap_search_s(CTX2LP(context), "", LDAP_SCOPE_BASE, 365 "objectclass=*", attr, 0, &m); 366 free(attr); 367 if (check_ldap(context, ret)) 368 goto fail; 369 370 if (ldap_count_entries(CTX2LP(context), m) > 0) { 371 m0 = ldap_first_entry(CTX2LP(context), m); 372 if (m0 == NULL) { 373 krb5_set_error_message(context->context, KADM5_RPC_ERROR, 374 "Error in AD ldap responce"); 375 ldap_msgfree(m); 376 goto fail; 377 } 378 vals = ldap_get_values(CTX2LP(context), 379 m0, "defaultNamingContext"); 380 if (vals == NULL) { 381 krb5_set_error_message(context->context, KADM5_RPC_ERROR, 382 "No naming context found"); 383 goto fail; 384 } 385 context->base_dn = strdup(vals[0]); 386 } else 387 goto fail; 388 ldap_msgfree(m); 389 } 390 391 for (i = 0; i < num_servers; i++) 392 free(servers[i].server); 393 free(servers); 394 395 return 0; 396 397 fail: 398 for (i = 0; i < num_servers; i++) 399 free(servers[i].server); 400 free(servers); 401 402 if (context->ldap_conn) { 403 ldap_unbind(CTX2LP(context)); 404 context->ldap_conn = NULL; 405 } 406 return KADM5_RPC_ERROR; 407} 408 409#define NTTIME_EPOCH 0x019DB1DED53E8000LL 410 411static time_t 412nt2unixtime(const char *str) 413{ 414 unsigned long long t; 415 t = strtoll(str, NULL, 10); 416 t = ((t - NTTIME_EPOCH) / (long long)10000000); 417 if (t > (((time_t)(~(long long)0)) >> 1)) 418 return 0; 419 return (time_t)t; 420} 421 422static long long 423unix2nttime(time_t unix_time) 424{ 425 long long wt; 426 wt = unix_time * (long long)10000000 + (long long)NTTIME_EPOCH; 427 return wt; 428} 429 430/* XXX create filter in a better way */ 431 432static int 433ad_find_entry(kadm5_ad_context *context, 434 const char *fqdn, 435 const char *pn, 436 char **name) 437{ 438 LDAPMessage *m, *m0; 439 char *attr[] = { "distinguishedName", NULL }; 440 char *filter; 441 int ret; 442 443 if (name) 444 *name = NULL; 445 446 if (fqdn) 447 asprintf(&filter, 448 "(&(objectClass=computer)(|(dNSHostName=%s)(servicePrincipalName=%s)))", 449 fqdn, pn); 450 else if(pn) 451 asprintf(&filter, "(&(objectClass=account)(userPrincipalName=%s))", pn); 452 else 453 return KADM5_RPC_ERROR; 454 455 ret = ldap_search_s(CTX2LP(context), CTX2BASE(context), 456 LDAP_SCOPE_SUBTREE, 457 filter, attr, 0, &m); 458 free(filter); 459 if (check_ldap(context, ret)) 460 return KADM5_RPC_ERROR; 461 462 if (ldap_count_entries(CTX2LP(context), m) > 0) { 463 char **vals; 464 m0 = ldap_first_entry(CTX2LP(context), m); 465 vals = ldap_get_values(CTX2LP(context), m0, "distinguishedName"); 466 if (vals == NULL || vals[0] == NULL) { 467 ldap_msgfree(m); 468 return KADM5_RPC_ERROR; 469 } 470 if (name) 471 *name = strdup(vals[0]); 472 ldap_msgfree(m); 473 } else 474 return KADM5_UNK_PRINC; 475 476 return 0; 477} 478 479#endif /* OPENLDAP */ 480 481static kadm5_ret_t 482ad_get_cred(kadm5_ad_context *context, const char *password) 483{ 484 kadm5_ret_t ret; 485 krb5_ccache cc; 486 char *service; 487 488 if (context->ccache) 489 return 0; 490 491 asprintf(&service, "%s/%s@%s", KRB5_TGS_NAME, 492 context->realm, context->realm); 493 if (service == NULL) 494 return ENOMEM; 495 496 ret = _kadm5_c_get_cred_cache(context->context, 497 context->client_name, 498 service, 499 password, krb5_prompter_posix, 500 NULL, NULL, &cc); 501 free(service); 502 if(ret) 503 return ret; /* XXX */ 504 context->ccache = cc; 505 return 0; 506} 507 508static kadm5_ret_t 509kadm5_ad_chpass_principal(void *server_handle, 510 krb5_principal principal, 511 int keepold, 512 const char *password, 513 int n_ks_tuple, 514 krb5_key_salt_tuple *ks_tuple) 515{ 516 kadm5_ad_context *context = server_handle; 517 krb5_data result_code_string, result_string; 518 int result_code; 519 kadm5_ret_t ret; 520 521 if (keepold) 522 return KADM5_KEEPOLD_NOSUPP; 523 524 ret = ad_get_cred(context, NULL); 525 if (ret) 526 return ret; 527 528 krb5_data_zero (&result_code_string); 529 krb5_data_zero (&result_string); 530 531 ret = krb5_set_password_using_ccache (context->context, 532 context->ccache, 533 password, 534 principal, 535 &result_code, 536 &result_code_string, 537 &result_string); 538 539 krb5_data_free (&result_code_string); 540 krb5_data_free (&result_string); 541 542 /* XXX do mapping here on error codes */ 543 544 return ret; 545} 546 547#ifdef OPENLDAP 548static const char * 549get_fqdn(krb5_context context, const krb5_principal p) 550{ 551 const char *s, *hosttypes[] = { "host", "ldap", "gc", "cifs", "dns" }; 552 int i; 553 554 s = krb5_principal_get_comp_string(context, p, 0); 555 if (p == NULL) 556 return NULL; 557 558 for (i = 0; i < sizeof(hosttypes)/sizeof(hosttypes[0]); i++) { 559 if (strcasecmp(s, hosttypes[i]) == 0) 560 return krb5_principal_get_comp_string(context, p, 1); 561 } 562 return 0; 563} 564#endif 565 566 567static kadm5_ret_t 568kadm5_ad_create_principal(void *server_handle, 569 kadm5_principal_ent_t entry, 570 uint32_t mask, 571 const char *password, 572 int n_ks_tuple, 573 krb5_key_salt_tuple *ks_tuple) 574{ 575 kadm5_ad_context *context = server_handle; 576 577 /* 578 * KADM5_PRINC_EXPIRE_TIME 579 * 580 * return 0 || KADM5_DUP; 581 */ 582 583#ifdef OPENLDAP 584 LDAPMod *attrs[8], rattrs[7], *a; 585 char *useraccvals[2] = { NULL, NULL }, 586 *samvals[2], *dnsvals[2], *spnvals[5], *upnvals[2], *tv[2]; 587 char *ocvals_spn[] = { "top", "person", "organizationalPerson", 588 "user", "computer", NULL}; 589 char *p, *realmless_p, *p_msrealm = NULL, *dn = NULL; 590 const char *fqdn; 591 char *s, *samname = NULL, *short_spn = NULL; 592 int ret, i; 593 int32_t uf_flags = 0; 594 595 if ((mask & KADM5_PRINCIPAL) == 0) 596 return KADM5_BAD_MASK; 597 598 for (i = 0; i < sizeof(rattrs)/sizeof(rattrs[0]); i++) 599 attrs[i] = &rattrs[i]; 600 attrs[i] = NULL; 601 602 ret = ad_get_cred(context, NULL); 603 if (ret) 604 return ret; 605 606 ret = _kadm5_ad_connect(server_handle); 607 if (ret) 608 return ret; 609 610 fqdn = get_fqdn(context->context, entry->principal); 611 612 ret = krb5_unparse_name(context->context, entry->principal, &p); 613 if (ret) 614 return ret; 615 616 if (ad_find_entry(context, fqdn, p, NULL) == 0) { 617 free(p); 618 return KADM5_DUP; 619 } 620 621 if (mask & KADM5_ATTRIBUTES) { 622 if (entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX) 623 uf_flags |= UF_ACCOUNTDISABLE|UF_LOCKOUT; 624 if ((entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH) == 0) 625 uf_flags |= UF_DONT_REQUIRE_PREAUTH; 626 if (entry->attributes & KRB5_KDB_REQUIRES_HW_AUTH) 627 uf_flags |= UF_SMARTCARD_REQUIRED; 628 } 629 630 realmless_p = strdup(p); 631 if (realmless_p == NULL) { 632 ret = ENOMEM; 633 goto out; 634 } 635 s = strrchr(realmless_p, '@'); 636 if (s) 637 *s = '\0'; 638 639 if (fqdn) { 640 /* create computer account */ 641 asprintf(&samname, "%s$", fqdn); 642 if (samname == NULL) { 643 ret = ENOMEM; 644 goto out; 645 } 646 s = strchr(samname, '.'); 647 if (s) { 648 s[0] = '$'; 649 s[1] = '\0'; 650 } 651 652 short_spn = strdup(p); 653 if (short_spn == NULL) { 654 errno = ENOMEM; 655 goto out; 656 } 657 s = strchr(short_spn, '.'); 658 if (s) { 659 *s = '\0'; 660 } else { 661 free(short_spn); 662 short_spn = NULL; 663 } 664 665 p_msrealm = strdup(p); 666 if (p_msrealm == NULL) { 667 errno = ENOMEM; 668 goto out; 669 } 670 s = strrchr(p_msrealm, '@'); 671 if (s) { 672 *s = '/'; 673 } else { 674 free(p_msrealm); 675 p_msrealm = NULL; 676 } 677 678 asprintf(&dn, "cn=%s, cn=Computers, %s", fqdn, CTX2BASE(context)); 679 if (dn == NULL) { 680 ret = ENOMEM; 681 goto out; 682 } 683 684 a = &rattrs[0]; 685 a->mod_op = LDAP_MOD_ADD; 686 a->mod_type = "objectClass"; 687 a->mod_values = ocvals_spn; 688 a++; 689 690 a->mod_op = LDAP_MOD_ADD; 691 a->mod_type = "userAccountControl"; 692 a->mod_values = useraccvals; 693 asprintf(&useraccvals[0], "%d", 694 uf_flags | 695 UF_PASSWD_NOT_EXPIRE | 696 UF_WORKSTATION_TRUST_ACCOUNT); 697 useraccvals[1] = NULL; 698 a++; 699 700 a->mod_op = LDAP_MOD_ADD; 701 a->mod_type = "sAMAccountName"; 702 a->mod_values = samvals; 703 samvals[0] = samname; 704 samvals[1] = NULL; 705 a++; 706 707 a->mod_op = LDAP_MOD_ADD; 708 a->mod_type = "dNSHostName"; 709 a->mod_values = dnsvals; 710 dnsvals[0] = (char *)fqdn; 711 dnsvals[1] = NULL; 712 a++; 713 714 /* XXX add even more spn's */ 715 a->mod_op = LDAP_MOD_ADD; 716 a->mod_type = "servicePrincipalName"; 717 a->mod_values = spnvals; 718 i = 0; 719 spnvals[i++] = p; 720 spnvals[i++] = realmless_p; 721 if (short_spn) 722 spnvals[i++] = short_spn; 723 if (p_msrealm) 724 spnvals[i++] = p_msrealm; 725 spnvals[i++] = NULL; 726 a++; 727 728 a->mod_op = LDAP_MOD_ADD; 729 a->mod_type = "userPrincipalName"; 730 a->mod_values = upnvals; 731 upnvals[0] = p; 732 upnvals[1] = NULL; 733 a++; 734 735 a->mod_op = LDAP_MOD_ADD; 736 a->mod_type = "accountExpires"; 737 a->mod_values = tv; 738 tv[0] = "9223372036854775807"; /* "never" */ 739 tv[1] = NULL; 740 a++; 741 742 } else { 743 /* create user account */ 744 745 a = &rattrs[0]; 746 a->mod_op = LDAP_MOD_ADD; 747 a->mod_type = "userAccountControl"; 748 a->mod_values = useraccvals; 749 asprintf(&useraccvals[0], "%d", 750 uf_flags | 751 UF_PASSWD_NOT_EXPIRE); 752 useraccvals[1] = NULL; 753 a++; 754 755 a->mod_op = LDAP_MOD_ADD; 756 a->mod_type = "sAMAccountName"; 757 a->mod_values = samvals; 758 samvals[0] = realmless_p; 759 samvals[1] = NULL; 760 a++; 761 762 a->mod_op = LDAP_MOD_ADD; 763 a->mod_type = "userPrincipalName"; 764 a->mod_values = upnvals; 765 upnvals[0] = p; 766 upnvals[1] = NULL; 767 a++; 768 769 a->mod_op = LDAP_MOD_ADD; 770 a->mod_type = "accountExpires"; 771 a->mod_values = tv; 772 tv[0] = "9223372036854775807"; /* "never" */ 773 tv[1] = NULL; 774 a++; 775 } 776 777 attrs[a - &rattrs[0]] = NULL; 778 779 ret = ldap_add_s(CTX2LP(context), dn, attrs); 780 781 out: 782 if (useraccvals[0]) 783 free(useraccvals[0]); 784 if (realmless_p) 785 free(realmless_p); 786 if (samname) 787 free(samname); 788 if (short_spn) 789 free(short_spn); 790 if (p_msrealm) 791 free(p_msrealm); 792 free(p); 793 794 if (check_ldap(context, ret)) 795 return KADM5_RPC_ERROR; 796 797 return 0; 798#else 799 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 800 return KADM5_RPC_ERROR; 801#endif 802} 803 804static kadm5_ret_t 805kadm5_ad_delete_principal(void *server_handle, krb5_principal principal) 806{ 807 kadm5_ad_context *context = server_handle; 808#ifdef OPENLDAP 809 char *p, *dn = NULL; 810 const char *fqdn; 811 int ret; 812 813 ret = ad_get_cred(context, NULL); 814 if (ret) 815 return ret; 816 817 ret = _kadm5_ad_connect(server_handle); 818 if (ret) 819 return ret; 820 821 fqdn = get_fqdn(context->context, principal); 822 823 ret = krb5_unparse_name(context->context, principal, &p); 824 if (ret) 825 return ret; 826 827 if (ad_find_entry(context, fqdn, p, &dn) != 0) { 828 free(p); 829 return KADM5_UNK_PRINC; 830 } 831 832 ret = ldap_delete_s(CTX2LP(context), dn); 833 834 free(dn); 835 free(p); 836 837 if (check_ldap(context, ret)) 838 return KADM5_RPC_ERROR; 839 return 0; 840#else 841 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 842 return KADM5_RPC_ERROR; 843#endif 844} 845 846static kadm5_ret_t 847kadm5_ad_destroy(void *server_handle) 848{ 849 kadm5_ad_context *context = server_handle; 850 851 if (context->ccache) 852 krb5_cc_destroy(context->context, context->ccache); 853 854#ifdef OPENLDAP 855 { 856 LDAP *lp = CTX2LP(context); 857 if (lp) 858 ldap_unbind(lp); 859 if (context->base_dn) 860 free(context->base_dn); 861 } 862#endif 863 free(context->realm); 864 free(context->client_name); 865 krb5_free_principal(context->context, context->caller); 866 if(context->my_context) 867 krb5_free_context(context->context); 868 return 0; 869} 870 871static kadm5_ret_t 872kadm5_ad_flush(void *server_handle) 873{ 874 kadm5_ad_context *context = server_handle; 875 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 876 return KADM5_RPC_ERROR; 877} 878 879static kadm5_ret_t 880kadm5_ad_get_principal(void *server_handle, 881 krb5_principal principal, 882 kadm5_principal_ent_t entry, 883 uint32_t mask) 884{ 885 kadm5_ad_context *context = server_handle; 886#ifdef OPENLDAP 887 LDAPMessage *m, *m0; 888 char **attr = NULL; 889 int attrlen = 0; 890 char *filter, *p, *q, *u; 891 int ret; 892 893 /* 894 * principal 895 * KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES 896 */ 897 898 /* 899 * return 0 || KADM5_DUP; 900 */ 901 902 memset(entry, 0, sizeof(*entry)); 903 904 if (mask & KADM5_KVNO) 905 laddattr(&attr, &attrlen, "msDS-KeyVersionNumber"); 906 907 if (mask & KADM5_PRINCIPAL) { 908 laddattr(&attr, &attrlen, "userPrincipalName"); 909 laddattr(&attr, &attrlen, "servicePrincipalName"); 910 } 911 laddattr(&attr, &attrlen, "objectClass"); 912 laddattr(&attr, &attrlen, "lastLogon"); 913 laddattr(&attr, &attrlen, "badPwdCount"); 914 laddattr(&attr, &attrlen, "badPasswordTime"); 915 laddattr(&attr, &attrlen, "pwdLastSet"); 916 laddattr(&attr, &attrlen, "accountExpires"); 917 laddattr(&attr, &attrlen, "userAccountControl"); 918 919 krb5_unparse_name_short(context->context, principal, &p); 920 krb5_unparse_name(context->context, principal, &u); 921 922 /* replace @ in domain part with a / */ 923 q = strrchr(p, '@'); 924 if (q && (p != q && *(q - 1) != '\\')) 925 *q = '/'; 926 927 asprintf(&filter, 928 "(|(userPrincipalName=%s)(servicePrincipalName=%s)(servicePrincipalName=%s))", 929 u, p, u); 930 free(p); 931 free(u); 932 933 ret = ldap_search_s(CTX2LP(context), CTX2BASE(context), 934 LDAP_SCOPE_SUBTREE, 935 filter, attr, 0, &m); 936 free(attr); 937 if (check_ldap(context, ret)) 938 return KADM5_RPC_ERROR; 939 940 if (ldap_count_entries(CTX2LP(context), m) > 0) { 941 char **vals; 942 m0 = ldap_first_entry(CTX2LP(context), m); 943 if (m0 == NULL) { 944 ldap_msgfree(m); 945 goto fail; 946 } 947#if 0 948 vals = ldap_get_values(CTX2LP(context), m0, "servicePrincipalName"); 949 if (vals) 950 printf("servicePrincipalName %s\n", vals[0]); 951 vals = ldap_get_values(CTX2LP(context), m0, "userPrincipalName"); 952 if (vals) 953 printf("userPrincipalName %s\n", vals[0]); 954 vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl"); 955 if (vals) 956 printf("userAccountControl %s\n", vals[0]); 957#endif 958 entry->princ_expire_time = 0; 959 if (mask & KADM5_PRINC_EXPIRE_TIME) { 960 vals = ldap_get_values(CTX2LP(context), m0, "accountExpires"); 961 if (vals) 962 entry->princ_expire_time = nt2unixtime(vals[0]); 963 } 964 entry->last_success = 0; 965 if (mask & KADM5_LAST_SUCCESS) { 966 vals = ldap_get_values(CTX2LP(context), m0, "lastLogon"); 967 if (vals) 968 entry->last_success = nt2unixtime(vals[0]); 969 } 970 if (mask & KADM5_LAST_FAILED) { 971 vals = ldap_get_values(CTX2LP(context), m0, "badPasswordTime"); 972 if (vals) 973 entry->last_failed = nt2unixtime(vals[0]); 974 } 975 if (mask & KADM5_LAST_PWD_CHANGE) { 976 vals = ldap_get_values(CTX2LP(context), m0, "pwdLastSet"); 977 if (vals) 978 entry->last_pwd_change = nt2unixtime(vals[0]); 979 } 980 if (mask & KADM5_FAIL_AUTH_COUNT) { 981 vals = ldap_get_values(CTX2LP(context), m0, "badPwdCount"); 982 if (vals) 983 entry->fail_auth_count = atoi(vals[0]); 984 } 985 if (mask & KADM5_ATTRIBUTES) { 986 vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl"); 987 if (vals) { 988 uint32_t i; 989 i = atoi(vals[0]); 990 if (i & (UF_ACCOUNTDISABLE|UF_LOCKOUT)) 991 entry->attributes |= KRB5_KDB_DISALLOW_ALL_TIX; 992 if ((i & UF_DONT_REQUIRE_PREAUTH) == 0) 993 entry->attributes |= KRB5_KDB_REQUIRES_PRE_AUTH; 994 if (i & UF_SMARTCARD_REQUIRED) 995 entry->attributes |= KRB5_KDB_REQUIRES_HW_AUTH; 996 if ((i & UF_WORKSTATION_TRUST_ACCOUNT) == 0) 997 entry->attributes |= KRB5_KDB_DISALLOW_SVR; 998 } 999 } 1000 if (mask & KADM5_KVNO) { 1001 vals = ldap_get_values(CTX2LP(context), m0, 1002 "msDS-KeyVersionNumber"); 1003 if (vals) 1004 entry->kvno = atoi(vals[0]); 1005 else 1006 entry->kvno = 0; 1007 } 1008 ldap_msgfree(m); 1009 } else { 1010 return KADM5_UNK_PRINC; 1011 } 1012 1013 if (mask & KADM5_PRINCIPAL) 1014 krb5_copy_principal(context->context, principal, &entry->principal); 1015 1016 return 0; 1017 fail: 1018 return KADM5_RPC_ERROR; 1019#else 1020 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1021 return KADM5_RPC_ERROR; 1022#endif 1023} 1024 1025static kadm5_ret_t 1026kadm5_ad_get_principals(void *server_handle, 1027 const char *expression, 1028 char ***principals, 1029 int *count) 1030{ 1031 kadm5_ad_context *context = server_handle; 1032 1033 /* 1034 * KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES 1035 */ 1036 1037#ifdef OPENLDAP 1038 kadm5_ret_t ret; 1039 1040 ret = ad_get_cred(context, NULL); 1041 if (ret) 1042 return ret; 1043 1044 ret = _kadm5_ad_connect(server_handle); 1045 if (ret) 1046 return ret; 1047 1048 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1049 return KADM5_RPC_ERROR; 1050#else 1051 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1052 return KADM5_RPC_ERROR; 1053#endif 1054} 1055 1056static kadm5_ret_t 1057kadm5_ad_get_privs(void *server_handle, uint32_t*privs) 1058{ 1059 kadm5_ad_context *context = server_handle; 1060 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1061 return KADM5_RPC_ERROR; 1062} 1063 1064static kadm5_ret_t 1065kadm5_ad_modify_principal(void *server_handle, 1066 kadm5_principal_ent_t entry, 1067 uint32_t mask) 1068{ 1069 kadm5_ad_context *context = server_handle; 1070 1071 /* 1072 * KADM5_ATTRIBUTES 1073 * KRB5_KDB_DISALLOW_ALL_TIX (| KADM5_KVNO) 1074 */ 1075 1076#ifdef OPENLDAP 1077 LDAPMessage *m = NULL, *m0; 1078 kadm5_ret_t ret; 1079 char **attr = NULL; 1080 int attrlen = 0; 1081 char *p = NULL, *s = NULL, *q; 1082 char **vals; 1083 LDAPMod *attrs[4], rattrs[3], *a; 1084 char *uaf[2] = { NULL, NULL }; 1085 char *kvno[2] = { NULL, NULL }; 1086 char *tv[2] = { NULL, NULL }; 1087 char *filter, *dn; 1088 int i; 1089 1090 for (i = 0; i < sizeof(rattrs)/sizeof(rattrs[0]); i++) 1091 attrs[i] = &rattrs[i]; 1092 attrs[i] = NULL; 1093 a = &rattrs[0]; 1094 1095 ret = _kadm5_ad_connect(server_handle); 1096 if (ret) 1097 return ret; 1098 1099 if (mask & KADM5_KVNO) 1100 laddattr(&attr, &attrlen, "msDS-KeyVersionNumber"); 1101 if (mask & KADM5_PRINC_EXPIRE_TIME) 1102 laddattr(&attr, &attrlen, "accountExpires"); 1103 if (mask & KADM5_ATTRIBUTES) 1104 laddattr(&attr, &attrlen, "userAccountControl"); 1105 laddattr(&attr, &attrlen, "distinguishedName"); 1106 1107 krb5_unparse_name(context->context, entry->principal, &p); 1108 1109 s = strdup(p); 1110 1111 q = strrchr(s, '@'); 1112 if (q && (p != q && *(q - 1) != '\\')) 1113 *q = '\0'; 1114 1115 asprintf(&filter, 1116 "(|(userPrincipalName=%s)(servicePrincipalName=%s))", 1117 s, s); 1118 free(p); 1119 free(s); 1120 1121 ret = ldap_search_s(CTX2LP(context), CTX2BASE(context), 1122 LDAP_SCOPE_SUBTREE, 1123 filter, attr, 0, &m); 1124 free(attr); 1125 free(filter); 1126 if (check_ldap(context, ret)) 1127 return KADM5_RPC_ERROR; 1128 1129 if (ldap_count_entries(CTX2LP(context), m) <= 0) { 1130 ret = KADM5_RPC_ERROR; 1131 goto out; 1132 } 1133 1134 m0 = ldap_first_entry(CTX2LP(context), m); 1135 1136 if (mask & KADM5_ATTRIBUTES) { 1137 int32_t i; 1138 1139 vals = ldap_get_values(CTX2LP(context), m0, "userAccountControl"); 1140 if (vals == NULL) { 1141 ret = KADM5_RPC_ERROR; 1142 goto out; 1143 } 1144 1145 i = atoi(vals[0]); 1146 if (i == 0) 1147 return KADM5_RPC_ERROR; 1148 1149 if (entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX) 1150 i |= (UF_ACCOUNTDISABLE|UF_LOCKOUT); 1151 else 1152 i &= ~(UF_ACCOUNTDISABLE|UF_LOCKOUT); 1153 if (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH) 1154 i &= ~UF_DONT_REQUIRE_PREAUTH; 1155 else 1156 i |= UF_DONT_REQUIRE_PREAUTH; 1157 if (entry->attributes & KRB5_KDB_REQUIRES_HW_AUTH) 1158 i |= UF_SMARTCARD_REQUIRED; 1159 else 1160 i &= UF_SMARTCARD_REQUIRED; 1161 if (entry->attributes & KRB5_KDB_DISALLOW_SVR) 1162 i &= ~UF_WORKSTATION_TRUST_ACCOUNT; 1163 else 1164 i |= UF_WORKSTATION_TRUST_ACCOUNT; 1165 1166 asprintf(&uaf[0], "%d", i); 1167 1168 a->mod_op = LDAP_MOD_REPLACE; 1169 a->mod_type = "userAccountControl"; 1170 a->mod_values = uaf; 1171 a++; 1172 } 1173 1174 if (mask & KADM5_KVNO) { 1175 vals = ldap_get_values(CTX2LP(context), m0, "msDS-KeyVersionNumber"); 1176 if (vals == NULL) { 1177 entry->kvno = 0; 1178 } else { 1179 asprintf(&kvno[0], "%d", entry->kvno); 1180 1181 a->mod_op = LDAP_MOD_REPLACE; 1182 a->mod_type = "msDS-KeyVersionNumber"; 1183 a->mod_values = kvno; 1184 a++; 1185 } 1186 } 1187 1188 if (mask & KADM5_PRINC_EXPIRE_TIME) { 1189 long long wt; 1190 vals = ldap_get_values(CTX2LP(context), m0, "accountExpires"); 1191 if (vals == NULL) { 1192 ret = KADM5_RPC_ERROR; 1193 goto out; 1194 } 1195 1196 wt = unix2nttime(entry->princ_expire_time); 1197 1198 asprintf(&tv[0], "%llu", wt); 1199 1200 a->mod_op = LDAP_MOD_REPLACE; 1201 a->mod_type = "accountExpires"; 1202 a->mod_values = tv; 1203 a++; 1204 } 1205 1206 vals = ldap_get_values(CTX2LP(context), m0, "distinguishedName"); 1207 if (vals == NULL) { 1208 ret = KADM5_RPC_ERROR; 1209 goto out; 1210 } 1211 dn = vals[0]; 1212 1213 attrs[a - &rattrs[0]] = NULL; 1214 1215 ret = ldap_modify_s(CTX2LP(context), dn, attrs); 1216 if (check_ldap(context, ret)) 1217 return KADM5_RPC_ERROR; 1218 1219 out: 1220 if (m) 1221 ldap_msgfree(m); 1222 if (uaf[0]) 1223 free(uaf[0]); 1224 if (kvno[0]) 1225 free(kvno[0]); 1226 if (tv[0]) 1227 free(tv[0]); 1228 return ret; 1229#else 1230 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1231 return KADM5_RPC_ERROR; 1232#endif 1233} 1234 1235/*ARGSUSED*/ 1236static kadm5_ret_t 1237kadm5_ad_randkey_principal(void *server_handle, 1238 krb5_principal principal, 1239 krb5_boolean keepold, 1240 int n_ks_tuple, 1241 krb5_key_salt_tuple *ks_tuple, 1242 krb5_keyblock **keys, 1243 int *n_keys) 1244{ 1245 kadm5_ad_context *context = server_handle; 1246 1247 if (keepold) 1248 return KADM5_KEEPOLD_NOSUPP; 1249 1250 /* 1251 * random key 1252 */ 1253 1254#ifdef OPENLDAP 1255 krb5_data result_code_string, result_string; 1256 int result_code, plen; 1257 kadm5_ret_t ret; 1258 char *password; 1259 1260 *keys = NULL; 1261 *n_keys = 0; 1262 1263 { 1264 char p[64]; 1265 krb5_generate_random_block(p, sizeof(p)); 1266 plen = base64_encode(p, sizeof(p), &password); 1267 if (plen < 0) 1268 return ENOMEM; 1269 } 1270 1271 ret = ad_get_cred(context, NULL); 1272 if (ret) { 1273 free(password); 1274 return ret; 1275 } 1276 1277 krb5_data_zero (&result_code_string); 1278 krb5_data_zero (&result_string); 1279 1280 ret = krb5_set_password_using_ccache (context->context, 1281 context->ccache, 1282 password, 1283 principal, 1284 &result_code, 1285 &result_code_string, 1286 &result_string); 1287 1288 krb5_data_free (&result_code_string); 1289 krb5_data_free (&result_string); 1290 1291 if (ret == 0) { 1292 1293 *keys = malloc(sizeof(**keys) * 1); 1294 if (*keys == NULL) { 1295 ret = ENOMEM; 1296 goto out; 1297 } 1298 *n_keys = 1; 1299 1300 ret = krb5_string_to_key(context->context, 1301 ENCTYPE_ARCFOUR_HMAC_MD5, 1302 password, 1303 principal, 1304 &(*keys)[0]); 1305 memset(password, 0, sizeof(password)); 1306 if (ret) { 1307 free(*keys); 1308 *keys = NULL; 1309 *n_keys = 0; 1310 goto out; 1311 } 1312 } 1313 memset(password, 0, plen); 1314 free(password); 1315 out: 1316 return ret; 1317#else 1318 *keys = NULL; 1319 *n_keys = 0; 1320 1321 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1322 return KADM5_RPC_ERROR; 1323#endif 1324} 1325 1326static kadm5_ret_t 1327kadm5_ad_rename_principal(void *server_handle, 1328 krb5_principal from, 1329 krb5_principal to) 1330{ 1331 kadm5_ad_context *context = server_handle; 1332 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1333 return KADM5_RPC_ERROR; 1334} 1335 1336static kadm5_ret_t 1337kadm5_ad_chpass_principal_with_key(void *server_handle, 1338 krb5_principal princ, 1339 int keepold, 1340 int n_key_data, 1341 krb5_key_data *key_data) 1342{ 1343 kadm5_ad_context *context = server_handle; 1344 krb5_set_error_message(context->context, KADM5_RPC_ERROR, "Function not implemented"); 1345 return KADM5_RPC_ERROR; 1346} 1347 1348static kadm5_ret_t 1349kadm5_ad_lock(void *server_handle) 1350{ 1351 return ENOTSUP; 1352} 1353 1354static kadm5_ret_t 1355kadm5_ad_unlock(void *server_handle) 1356{ 1357 return ENOTSUP; 1358} 1359 1360static void 1361set_funcs(kadm5_ad_context *c) 1362{ 1363#define SET(C, F) (C)->funcs.F = kadm5_ad_ ## F 1364 SET(c, chpass_principal); 1365 SET(c, chpass_principal_with_key); 1366 SET(c, create_principal); 1367 SET(c, delete_principal); 1368 SET(c, destroy); 1369 SET(c, flush); 1370 SET(c, get_principal); 1371 SET(c, get_principals); 1372 SET(c, get_privs); 1373 SET(c, modify_principal); 1374 SET(c, randkey_principal); 1375 SET(c, rename_principal); 1376 SET(c, lock); 1377 SET(c, unlock); 1378} 1379 1380kadm5_ret_t 1381kadm5_ad_init_with_password_ctx(krb5_context context, 1382 const char *client_name, 1383 const char *password, 1384 const char *service_name, 1385 kadm5_config_params *realm_params, 1386 unsigned long struct_version, 1387 unsigned long api_version, 1388 void **server_handle) 1389{ 1390 kadm5_ret_t ret; 1391 kadm5_ad_context *ctx; 1392 1393 ctx = malloc(sizeof(*ctx)); 1394 if(ctx == NULL) 1395 return ENOMEM; 1396 memset(ctx, 0, sizeof(*ctx)); 1397 set_funcs(ctx); 1398 1399 ctx->context = context; 1400 krb5_add_et_list (context, initialize_kadm5_error_table_r); 1401 1402 ret = krb5_parse_name(ctx->context, client_name, &ctx->caller); 1403 if(ret) { 1404 free(ctx); 1405 return ret; 1406 } 1407 1408 if(realm_params->mask & KADM5_CONFIG_REALM) { 1409 ret = 0; 1410 ctx->realm = strdup(realm_params->realm); 1411 if (ctx->realm == NULL) 1412 ret = ENOMEM; 1413 } else 1414 ret = krb5_get_default_realm(ctx->context, &ctx->realm); 1415 if (ret) { 1416 free(ctx); 1417 return ret; 1418 } 1419 1420 ctx->client_name = strdup(client_name); 1421 1422 if(password != NULL && *password != '\0') 1423 ret = ad_get_cred(ctx, password); 1424 else 1425 ret = ad_get_cred(ctx, NULL); 1426 if(ret) { 1427 kadm5_ad_destroy(ctx); 1428 return ret; 1429 } 1430 1431#ifdef OPENLDAP 1432 ret = _kadm5_ad_connect(ctx); 1433 if (ret) { 1434 kadm5_ad_destroy(ctx); 1435 return ret; 1436 } 1437#endif 1438 1439 *server_handle = ctx; 1440 return 0; 1441} 1442 1443kadm5_ret_t 1444kadm5_ad_init_with_password(const char *client_name, 1445 const char *password, 1446 const char *service_name, 1447 kadm5_config_params *realm_params, 1448 unsigned long struct_version, 1449 unsigned long api_version, 1450 void **server_handle) 1451{ 1452 krb5_context context; 1453 kadm5_ret_t ret; 1454 kadm5_ad_context *ctx; 1455 1456 ret = krb5_init_context(&context); 1457 if (ret) 1458 return ret; 1459 ret = kadm5_ad_init_with_password_ctx(context, 1460 client_name, 1461 password, 1462 service_name, 1463 realm_params, 1464 struct_version, 1465 api_version, 1466 server_handle); 1467 if(ret) { 1468 krb5_free_context(context); 1469 return ret; 1470 } 1471 ctx = *server_handle; 1472 ctx->my_context = 1; 1473 return 0; 1474} 1475