PageRenderTime 78ms CodeModel.GetById 2ms app.highlight 67ms RepoModel.GetById 2ms app.codeStats 0ms

/release/src-rt-6.x.4708/router/samba3/source3/winbindd/winbindd_cred_cache.c

https://bitbucket.org/oglop/tomato-arm-kille72
C | 1065 lines | 728 code | 167 blank | 170 comment | 119 complexity | db63ec587d4673af74ed0d8ba4d5eeb9 MD5 | raw file
   1/*
   2   Unix SMB/CIFS implementation.
   3
   4   Winbind daemon - krb5 credential cache functions
   5   and in-memory cache functions.
   6
   7   Copyright (C) Guenther Deschner 2005-2006
   8   Copyright (C) Jeremy Allison 2006
   9
  10   This program is free software; you can redistribute it and/or modify
  11   it under the terms of the GNU General Public License as published by
  12   the Free Software Foundation; either version 3 of the License, or
  13   (at your option) any later version.
  14
  15   This program is distributed in the hope that it will be useful,
  16   but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18   GNU General Public License for more details.
  19
  20   You should have received a copy of the GNU General Public License
  21   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  22*/
  23
  24#include "includes.h"
  25#include "winbindd.h"
  26#include "../libcli/auth/libcli_auth.h"
  27#include "smb_krb5.h"
  28#include "libads/kerberos_proto.h"
  29
  30#undef DBGC_CLASS
  31#define DBGC_CLASS DBGC_WINBIND
  32
  33/* uncomment this to do fast debugging on the krb5 ticket renewal event */
  34#ifdef DEBUG_KRB5_TKT_RENEWAL
  35#undef DEBUG_KRB5_TKT_RENEWAL
  36#endif
  37
  38#define MAX_CCACHES 100
  39
  40static struct WINBINDD_CCACHE_ENTRY *ccache_list;
  41static void krb5_ticket_gain_handler(struct event_context *,
  42				     struct timed_event *,
  43				     struct timeval,
  44				     void *);
  45static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
  46				     struct timeval);
  47
  48/* The Krb5 ticket refresh handler should be scheduled
  49   at one-half of the period from now till the tkt
  50   expiration */
  51
  52static time_t krb5_event_refresh_time(time_t end_time)
  53{
  54	time_t rest = end_time - time(NULL);
  55	return end_time - rest/2;
  56}
  57
  58/****************************************************************
  59 Find an entry by name.
  60****************************************************************/
  61
  62static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
  63{
  64	struct WINBINDD_CCACHE_ENTRY *entry;
  65
  66	for (entry = ccache_list; entry; entry = entry->next) {
  67		if (strequal(entry->username, username)) {
  68			return entry;
  69		}
  70	}
  71	return NULL;
  72}
  73
  74/****************************************************************
  75 How many do we have ?
  76****************************************************************/
  77
  78static int ccache_entry_count(void)
  79{
  80	struct WINBINDD_CCACHE_ENTRY *entry;
  81	int i = 0;
  82
  83	for (entry = ccache_list; entry; entry = entry->next) {
  84		i++;
  85	}
  86	return i;
  87}
  88
  89void ccache_remove_all_after_fork(void)
  90{
  91	struct WINBINDD_CCACHE_ENTRY *cur, *next;
  92
  93	for (cur = ccache_list; cur; cur = next) {
  94		next = cur->next;
  95		DLIST_REMOVE(ccache_list, cur);
  96		TALLOC_FREE(cur->event);
  97		TALLOC_FREE(cur);
  98	}
  99
 100	return;
 101}
 102
 103/****************************************************************
 104 Do the work of refreshing the ticket.
 105****************************************************************/
 106
 107static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
 108					struct timed_event *te,
 109					struct timeval now,
 110					void *private_data)
 111{
 112	struct WINBINDD_CCACHE_ENTRY *entry =
 113		talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
 114#ifdef HAVE_KRB5
 115	int ret;
 116	time_t new_start;
 117	time_t expire_time = 0;
 118	struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
 119#endif
 120
 121	DEBUG(10,("krb5_ticket_refresh_handler called\n"));
 122	DEBUGADD(10,("event called for: %s, %s\n",
 123		entry->ccname, entry->username));
 124
 125	TALLOC_FREE(entry->event);
 126
 127#ifdef HAVE_KRB5
 128
 129	/* Kinit again if we have the user password and we can't renew the old
 130	 * tgt anymore 
 131	 * NB
 132	 * This happens when machine are put to sleep for a very long time. */
 133
 134	if (entry->renew_until < time(NULL)) {
 135rekinit:
 136		if (cred_ptr && cred_ptr->pass) {
 137
 138			set_effective_uid(entry->uid);
 139
 140			ret = kerberos_kinit_password_ext(entry->principal_name,
 141							  cred_ptr->pass,
 142							  0, /* hm, can we do time correction here ? */
 143							  &entry->refresh_time,
 144							  &entry->renew_until,
 145							  entry->ccname,
 146							  False, /* no PAC required anymore */
 147							  True,
 148							  WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
 149							  NULL);
 150			gain_root_privilege();
 151
 152			if (ret) {
 153				DEBUG(3,("krb5_ticket_refresh_handler: "
 154					"could not re-kinit: %s\n",
 155					error_message(ret)));
 156				/* destroy the ticket because we cannot rekinit
 157				 * it, ignore error here */
 158				ads_kdestroy(entry->ccname);
 159
 160				/* Don't break the ticket refresh chain: retry 
 161				 * refreshing ticket sometime later when KDC is 
 162				 * unreachable -- BoYang. More error code handling
 163				 * here? 
 164				 * */
 165
 166				if ((ret == KRB5_KDC_UNREACH)
 167				    || (ret == KRB5_REALM_CANT_RESOLVE)) {
 168#if defined(DEBUG_KRB5_TKT_RENEWAL)
 169					new_start = time(NULL) + 30;
 170#else
 171					new_start = time(NULL) +
 172						    MAX(30, lp_winbind_cache_time());
 173#endif
 174					add_krb5_ticket_gain_handler_event(entry,
 175							timeval_set(new_start, 0));
 176					return;
 177				}
 178				TALLOC_FREE(entry->event);
 179				return;
 180			}
 181
 182			DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
 183				"for: %s in ccache: %s\n",
 184				entry->principal_name, entry->ccname));
 185
 186#if defined(DEBUG_KRB5_TKT_RENEWAL)
 187			new_start = time(NULL) + 30;
 188#else
 189			/* The tkt should be refreshed at one-half the period
 190			   from now to the expiration time */
 191			expire_time = entry->refresh_time;
 192			new_start = krb5_event_refresh_time(entry->refresh_time);
 193#endif
 194			goto done;
 195		} else {
 196				/* can this happen? 
 197				 * No cached credentials
 198				 * destroy ticket and refresh chain 
 199				 * */
 200				ads_kdestroy(entry->ccname);
 201				TALLOC_FREE(entry->event);
 202				return;
 203		}
 204	}
 205
 206	set_effective_uid(entry->uid);
 207
 208	ret = smb_krb5_renew_ticket(entry->ccname,
 209				    entry->principal_name,
 210				    entry->service,
 211				    &new_start);
 212#if defined(DEBUG_KRB5_TKT_RENEWAL)
 213	new_start = time(NULL) + 30;
 214#else
 215	expire_time = new_start;
 216	new_start = krb5_event_refresh_time(new_start);
 217#endif
 218
 219	gain_root_privilege();
 220
 221	if (ret) {
 222		DEBUG(3,("krb5_ticket_refresh_handler: "
 223			"could not renew tickets: %s\n",
 224			error_message(ret)));
 225		/* maybe we are beyond the renewing window */
 226
 227		/* evil rises here, we refresh ticket failed,
 228		 * but the ticket might be expired. Therefore,
 229		 * When we refresh ticket failed, destory the 
 230		 * ticket */
 231
 232		ads_kdestroy(entry->ccname);
 233
 234		/* avoid breaking the renewal chain: retry in
 235		 * lp_winbind_cache_time() seconds when the KDC was not
 236		 * available right now. 
 237		 * the return code can be KRB5_REALM_CANT_RESOLVE. 
 238		 * More error code handling here? */
 239
 240		if ((ret == KRB5_KDC_UNREACH) 
 241		    || (ret == KRB5_REALM_CANT_RESOLVE)) {
 242#if defined(DEBUG_KRB5_TKT_RENEWAL)
 243			new_start = time(NULL) + 30;
 244#else
 245			new_start = time(NULL) +
 246				    MAX(30, lp_winbind_cache_time());
 247#endif
 248			/* ticket is destroyed here, we have to regain it
 249			 * if it is possible */
 250			add_krb5_ticket_gain_handler_event(entry,
 251						timeval_set(new_start, 0));
 252			return;
 253		}
 254
 255		/* This is evil, if the ticket was already expired.
 256		 * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
 257		 * But there is still a chance that we can rekinit it. 
 258		 *
 259		 * This happens when user login in online mode, and then network
 260		 * down or something cause winbind goes offline for a very long time,
 261		 * and then goes online again. ticket expired, renew failed.
 262		 * This happens when machine are put to sleep for a long time,
 263		 * but shorter than entry->renew_util.
 264		 * NB
 265		 * Looks like the KDC is reachable, we want to rekinit as soon as
 266		 * possible instead of waiting some time later. */
 267		if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
 268		    || (ret == KRB5_FCC_NOFILE)) goto rekinit;
 269
 270		return;
 271	}
 272
 273done:
 274	/* in cases that ticket will be unrenewable soon, we don't try to renew ticket 
 275	 * but try to regain ticket if it is possible */
 276	if (entry->renew_until && expire_time
 277	     && (entry->renew_until <= expire_time)) {
 278		/* try to regain ticket 10 seconds before expiration */
 279		expire_time -= 10;
 280		add_krb5_ticket_gain_handler_event(entry,
 281					timeval_set(expire_time, 0));
 282		return;
 283	}
 284
 285	if (entry->refresh_time == 0) {
 286		entry->refresh_time = new_start;
 287	}
 288	entry->event = event_add_timed(winbind_event_context(), entry,
 289				       timeval_set(new_start, 0),
 290				       krb5_ticket_refresh_handler,
 291				       entry);
 292
 293#endif
 294}
 295
 296/****************************************************************
 297 Do the work of regaining a ticket when coming from offline auth.
 298****************************************************************/
 299
 300static void krb5_ticket_gain_handler(struct event_context *event_ctx,
 301				     struct timed_event *te,
 302				     struct timeval now,
 303				     void *private_data)
 304{
 305	struct WINBINDD_CCACHE_ENTRY *entry =
 306		talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
 307#ifdef HAVE_KRB5
 308	int ret;
 309	struct timeval t;
 310	struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
 311	struct winbindd_domain *domain = NULL;
 312#endif
 313
 314	DEBUG(10,("krb5_ticket_gain_handler called\n"));
 315	DEBUGADD(10,("event called for: %s, %s\n",
 316		entry->ccname, entry->username));
 317
 318	TALLOC_FREE(entry->event);
 319
 320#ifdef HAVE_KRB5
 321
 322	if (!cred_ptr || !cred_ptr->pass) {
 323		DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
 324		return;
 325	}
 326
 327	if ((domain = find_domain_from_name(entry->realm)) == NULL) {
 328		DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
 329		return;
 330	}
 331
 332	if (!domain->online) {
 333		goto retry_later;
 334	}
 335
 336	set_effective_uid(entry->uid);
 337
 338	ret = kerberos_kinit_password_ext(entry->principal_name,
 339					  cred_ptr->pass,
 340					  0, /* hm, can we do time correction here ? */
 341					  &entry->refresh_time,
 342					  &entry->renew_until,
 343					  entry->ccname,
 344					  False, /* no PAC required anymore */
 345					  True,
 346					  WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
 347					  NULL);
 348	gain_root_privilege();
 349
 350	if (ret) {
 351		DEBUG(3,("krb5_ticket_gain_handler: "
 352			"could not kinit: %s\n",
 353			error_message(ret)));
 354		/* evil. If we cannot do it, destroy any the __maybe__ 
 355		 * __existing__ ticket */
 356		ads_kdestroy(entry->ccname);
 357		goto retry_later;
 358	}
 359
 360	DEBUG(10,("krb5_ticket_gain_handler: "
 361		"successful kinit for: %s in ccache: %s\n",
 362		entry->principal_name, entry->ccname));
 363
 364	goto got_ticket;
 365
 366  retry_later:
 367 
 368#if defined(DEBUG_KRB5_TKT_RENEWAL)
 369 	t = timeval_set(time(NULL) + 30, 0);
 370#else
 371	t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
 372#endif
 373
 374	add_krb5_ticket_gain_handler_event(entry, t);
 375	return;
 376
 377  got_ticket:
 378
 379#if defined(DEBUG_KRB5_TKT_RENEWAL)
 380	t = timeval_set(time(NULL) + 30, 0);
 381#else
 382	t = timeval_set(krb5_event_refresh_time(entry->refresh_time), 0);
 383#endif
 384
 385	if (entry->refresh_time == 0) {
 386		entry->refresh_time = t.tv_sec;
 387	}
 388	entry->event = event_add_timed(winbind_event_context(),
 389				       entry,
 390				       t,
 391				       krb5_ticket_refresh_handler,
 392				       entry);
 393
 394	return;
 395#endif
 396}
 397
 398/**************************************************************
 399 The gain initial ticket case is recognised as entry->refresh_time
 400 is always zero.
 401**************************************************************/
 402
 403static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
 404				     struct timeval t)
 405{
 406	entry->refresh_time = 0;
 407	entry->event = event_add_timed(winbind_event_context(),
 408				       entry,
 409				       t,
 410				       krb5_ticket_gain_handler,
 411				       entry);
 412}
 413
 414void ccache_regain_all_now(void)
 415{
 416	struct WINBINDD_CCACHE_ENTRY *cur;
 417	struct timeval t = timeval_current();
 418
 419	for (cur = ccache_list; cur; cur = cur->next) {
 420		struct timed_event *new_event;
 421
 422		/*
 423		 * if refresh_time is 0, we know that the
 424		 * the event has the krb5_ticket_gain_handler
 425		 */
 426		if (cur->refresh_time == 0) {
 427			new_event = event_add_timed(winbind_event_context(),
 428						    cur,
 429						    t,
 430						    krb5_ticket_gain_handler,
 431						    cur);
 432		} else {
 433			new_event = event_add_timed(winbind_event_context(),
 434						    cur,
 435						    t,
 436						    krb5_ticket_refresh_handler,
 437						    cur);
 438		}
 439
 440		if (!new_event) {
 441			continue;
 442		}
 443
 444		TALLOC_FREE(cur->event);
 445		cur->event = new_event;
 446	}
 447
 448	return;
 449}
 450
 451/****************************************************************
 452 Check if an ccache entry exists.
 453****************************************************************/
 454
 455bool ccache_entry_exists(const char *username)
 456{
 457	struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
 458	return (entry != NULL);
 459}
 460
 461/****************************************************************
 462 Ensure we're changing the correct entry.
 463****************************************************************/
 464
 465bool ccache_entry_identical(const char *username,
 466			    uid_t uid,
 467			    const char *ccname)
 468{
 469	struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
 470
 471	if (!entry) {
 472		return False;
 473	}
 474
 475	if (entry->uid != uid) {
 476		DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
 477			(unsigned int)entry->uid, (unsigned int)uid));
 478		return False;
 479	}
 480	if (!strcsequal(entry->ccname, ccname)) {
 481		DEBUG(0,("cache_entry_identical: "
 482			"ccnames differ: (cache) %s != (client) %s\n",
 483                        entry->ccname, ccname));
 484		return False;
 485	}
 486	return True;
 487}
 488
 489NTSTATUS add_ccache_to_list(const char *princ_name,
 490			    const char *ccname,
 491			    const char *service,
 492			    const char *username,
 493			    const char *pass,
 494			    const char *realm,
 495			    uid_t uid,
 496			    time_t create_time,
 497			    time_t ticket_end,
 498			    time_t renew_until,
 499			    bool postponed_request)
 500{
 501	struct WINBINDD_CCACHE_ENTRY *entry = NULL;
 502	struct timeval t;
 503	NTSTATUS ntret;
 504#ifdef HAVE_KRB5
 505	int ret;
 506#endif
 507
 508	if ((username == NULL && princ_name == NULL) ||
 509	    ccname == NULL || uid < 0) {
 510		return NT_STATUS_INVALID_PARAMETER;
 511	}
 512
 513	if (ccache_entry_count() + 1 > MAX_CCACHES) {
 514		DEBUG(10,("add_ccache_to_list: "
 515			"max number of ccaches reached\n"));
 516		return NT_STATUS_NO_MORE_ENTRIES;
 517	}
 518
 519	/* If it is cached login, destroy krb5 ticket
 520	 * to avoid surprise. */
 521#ifdef HAVE_KRB5
 522	if (postponed_request) {
 523		/* ignore KRB5_FCC_NOFILE error here */
 524		ret = ads_kdestroy(ccname);
 525		if (ret == KRB5_FCC_NOFILE) {
 526			ret = 0;
 527		}
 528		if (ret) {
 529			DEBUG(0, ("add_ccache_to_list: failed to destroy "
 530				   "user krb5 ccache %s with %s\n", ccname,
 531				   error_message(ret)));
 532			return krb5_to_nt_status(ret);
 533		}
 534		DEBUG(10, ("add_ccache_to_list: successfully destroyed "
 535			   "krb5 ccache %s for user %s\n", ccname,
 536			   username));
 537	}
 538#endif
 539
 540	/* Reference count old entries */
 541	entry = get_ccache_by_username(username);
 542	if (entry) {
 543		/* Check cached entries are identical. */
 544		if (!ccache_entry_identical(username, uid, ccname)) {
 545			return NT_STATUS_INVALID_PARAMETER;
 546		}
 547		entry->ref_count++;
 548		DEBUG(10,("add_ccache_to_list: "
 549			"ref count on entry %s is now %d\n",
 550			username, entry->ref_count));
 551		/* FIXME: in this case we still might want to have a krb5 cred
 552		 * event handler created - gd
 553		 * Add ticket refresh handler here */
 554
 555		if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
 556			return NT_STATUS_OK;
 557		}
 558
 559		if (!entry->event) {
 560			if (postponed_request) {
 561				t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
 562				add_krb5_ticket_gain_handler_event(entry, t);
 563			} else {
 564				/* Renew at 1/2 the ticket expiration time */
 565#if defined(DEBUG_KRB5_TKT_RENEWAL)
 566				t = timeval_set(time(NULL)+30, 0);
 567#else
 568				t = timeval_set(krb5_event_refresh_time(ticket_end),
 569						0);
 570#endif
 571				if (!entry->refresh_time) {
 572					entry->refresh_time = t.tv_sec;
 573				}
 574				entry->event = event_add_timed(winbind_event_context(),
 575							       entry,
 576							       t,
 577							       krb5_ticket_refresh_handler,
 578							       entry);
 579			}
 580
 581			if (!entry->event) {
 582				ntret = remove_ccache(username);
 583				if (!NT_STATUS_IS_OK(ntret)) {
 584					DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
 585						  "ccache %s for user %s\n", entry->ccname,
 586						  entry->username));
 587					DEBUG(0, ("add_ccache_to_list: error is %s\n",
 588						  nt_errstr(ntret)));
 589					return ntret;
 590				}
 591				return NT_STATUS_NO_MEMORY;
 592			}
 593
 594			DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
 595
 596		}
 597
 598		/*
 599		 * If we're set up to renew our krb5 tickets, we must
 600		 * cache the credentials in memory for the ticket
 601		 * renew function (or increase the reference count
 602		 * if we're logging in more than once). Fix inspired
 603		 * by patch from Ian Gordon <ian.gordon@strath.ac.uk>
 604		 * for bugid #9098.
 605		 */
 606
 607		ntret = winbindd_add_memory_creds(username, uid, pass);
 608		DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
 609			nt_errstr(ntret)));
 610
 611		return NT_STATUS_OK;
 612	}
 613
 614	entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
 615	if (!entry) {
 616		return NT_STATUS_NO_MEMORY;
 617	}
 618
 619	ZERO_STRUCTP(entry);
 620
 621	if (username) {
 622		entry->username = talloc_strdup(entry, username);
 623		if (!entry->username) {
 624			goto no_mem;
 625		}
 626	}
 627	if (princ_name) {
 628		entry->principal_name = talloc_strdup(entry, princ_name);
 629		if (!entry->principal_name) {
 630			goto no_mem;
 631		}
 632	}
 633	if (service) {
 634		entry->service = talloc_strdup(entry, service);
 635		if (!entry->service) {
 636			goto no_mem;
 637		}
 638	}
 639
 640	entry->ccname = talloc_strdup(entry, ccname);
 641	if (!entry->ccname) {
 642		goto no_mem;
 643	}
 644
 645	entry->realm = talloc_strdup(entry, realm);
 646	if (!entry->realm) {
 647		goto no_mem;
 648	}
 649
 650	entry->create_time = create_time;
 651	entry->renew_until = renew_until;
 652	entry->uid = uid;
 653	entry->ref_count = 1;
 654
 655	if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
 656		goto add_entry;
 657	}
 658
 659	if (postponed_request) {
 660		t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
 661		add_krb5_ticket_gain_handler_event(entry, t);
 662	} else {
 663		/* Renew at 1/2 the ticket expiration time */
 664#if defined(DEBUG_KRB5_TKT_RENEWAL)
 665		t = timeval_set(time(NULL)+30, 0);
 666#else
 667		t = timeval_set(krb5_event_refresh_time(ticket_end), 0);
 668#endif
 669		if (entry->refresh_time == 0) {
 670			entry->refresh_time = t.tv_sec;
 671		}
 672		entry->event = event_add_timed(winbind_event_context(),
 673					       entry,
 674					       t,
 675					       krb5_ticket_refresh_handler,
 676					       entry);
 677	}
 678
 679	if (!entry->event) {
 680		goto no_mem;
 681	}
 682
 683	DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
 684
 685 add_entry:
 686
 687	DLIST_ADD(ccache_list, entry);
 688
 689	DEBUG(10,("add_ccache_to_list: "
 690		"added ccache [%s] for user [%s] to the list\n",
 691		ccname, username));
 692
 693	if (entry->event) {
 694		/*
 695		 * If we're set up to renew our krb5 tickets, we must
 696		 * cache the credentials in memory for the ticket
 697		 * renew function. Fix inspired by patch from
 698		 * Ian Gordon <ian.gordon@strath.ac.uk> for
 699		 * bugid #9098.
 700		 */
 701
 702		ntret = winbindd_add_memory_creds(username, uid, pass);
 703		DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
 704			nt_errstr(ntret)));
 705	}
 706
 707	return NT_STATUS_OK;
 708
 709 no_mem:
 710
 711	TALLOC_FREE(entry);
 712	return NT_STATUS_NO_MEMORY;
 713}
 714
 715/*******************************************************************
 716 Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
 717 referenced.
 718 *******************************************************************/
 719
 720NTSTATUS remove_ccache(const char *username)
 721{
 722	struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
 723	NTSTATUS status = NT_STATUS_OK;
 724#ifdef HAVE_KRB5
 725	krb5_error_code ret;
 726#endif
 727
 728	if (!entry) {
 729		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
 730	}
 731
 732	if (entry->ref_count <= 0) {
 733		DEBUG(0,("remove_ccache: logic error. "
 734			"ref count for user %s = %d\n",
 735			username, entry->ref_count));
 736		return NT_STATUS_INTERNAL_DB_CORRUPTION;
 737	}
 738
 739	entry->ref_count--;
 740
 741	if (entry->ref_count > 0) {
 742		DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
 743			username, entry->ref_count));
 744		return NT_STATUS_OK;
 745	}
 746
 747	/* no references any more */
 748
 749	DLIST_REMOVE(ccache_list, entry);
 750	TALLOC_FREE(entry->event); /* unregisters events */
 751
 752#ifdef HAVE_KRB5
 753	ret = ads_kdestroy(entry->ccname);
 754
 755	/* we ignore the error when there has been no credential cache */
 756	if (ret == KRB5_FCC_NOFILE) {
 757		ret = 0;
 758	} else if (ret) {
 759		DEBUG(0,("remove_ccache: "
 760			"failed to destroy user krb5 ccache %s with: %s\n",
 761			entry->ccname, error_message(ret)));
 762	} else {
 763		DEBUG(10,("remove_ccache: "
 764			"successfully destroyed krb5 ccache %s for user %s\n",
 765			entry->ccname, username));
 766	}
 767	status = krb5_to_nt_status(ret);
 768#endif
 769
 770	TALLOC_FREE(entry);
 771 	DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
 772
 773	return status;
 774}
 775
 776/*******************************************************************
 777 In memory credentials cache code.
 778*******************************************************************/
 779
 780static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
 781
 782/***********************************************************
 783 Find an entry on the list by name.
 784***********************************************************/
 785
 786struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
 787{
 788	struct WINBINDD_MEMORY_CREDS *p;
 789
 790	for (p = memory_creds_list; p; p = p->next) {
 791		if (strequal(p->username, username)) {
 792			return p;
 793		}
 794	}
 795	return NULL;
 796}
 797
 798/***********************************************************
 799 Store the required creds and mlock them.
 800***********************************************************/
 801
 802static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
 803				   const char *pass)
 804{
 805#if !defined(HAVE_MLOCK)
 806	return NT_STATUS_OK;
 807#else
 808	/* new_entry->nt_hash is the base pointer for the block
 809	   of memory pointed into by new_entry->lm_hash and
 810	   new_entry->pass (if we're storing plaintext). */
 811
 812	memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
 813	if (pass) {
 814		memcredp->len += strlen(pass)+1;
 815	}
 816
 817
 818#if defined(LINUX)
 819	/* aligning the memory on on x86_64 and compiling
 820	   with gcc 4.1 using -O2 causes a segv in the
 821	   next memset()  --jerry */
 822	memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
 823#else
 824	/* On non-linux platforms, mlock()'d memory must be aligned */
 825	memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
 826					       getpagesize(), memcredp->len);
 827#endif
 828	if (!memcredp->nt_hash) {
 829		return NT_STATUS_NO_MEMORY;
 830	}
 831	memset(memcredp->nt_hash, 0x0, memcredp->len);
 832
 833	memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
 834
 835#ifdef DEBUG_PASSWORD
 836	DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
 837#endif
 838	if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
 839		DEBUG(0,("failed to mlock memory: %s (%d)\n",
 840			strerror(errno), errno));
 841		SAFE_FREE(memcredp->nt_hash);
 842		return map_nt_error_from_unix(errno);
 843	}
 844
 845#ifdef DEBUG_PASSWORD
 846	DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
 847#endif
 848
 849	/* Create and store the password hashes. */
 850	E_md4hash(pass, memcredp->nt_hash);
 851	E_deshash(pass, memcredp->lm_hash);
 852
 853	if (pass) {
 854		memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
 855		memcpy(memcredp->pass, pass,
 856		       memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
 857	}
 858
 859	return NT_STATUS_OK;
 860#endif
 861}
 862
 863/***********************************************************
 864 Destroy existing creds.
 865***********************************************************/
 866
 867static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
 868{
 869#if !defined(HAVE_MUNLOCK)
 870	return NT_STATUS_OK;
 871#else
 872	if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
 873		DEBUG(0,("failed to munlock memory: %s (%d)\n",
 874			strerror(errno), errno));
 875		return map_nt_error_from_unix(errno);
 876	}
 877	memset(memcredp->nt_hash, '\0', memcredp->len);
 878	SAFE_FREE(memcredp->nt_hash);
 879	memcredp->nt_hash = NULL;
 880	memcredp->lm_hash = NULL;
 881	memcredp->pass = NULL;
 882	memcredp->len = 0;
 883	return NT_STATUS_OK;
 884#endif
 885}
 886
 887/***********************************************************
 888 Replace the required creds with new ones (password change).
 889***********************************************************/
 890
 891static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
 892						       const char *pass)
 893{
 894	NTSTATUS status = delete_memory_creds(memcredp);
 895	if (!NT_STATUS_IS_OK(status)) {
 896		return status;
 897	}
 898	return store_memory_creds(memcredp, pass);
 899}
 900
 901/*************************************************************
 902 Store credentials in memory in a list.
 903*************************************************************/
 904
 905static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
 906						   uid_t uid,
 907						   const char *pass)
 908{
 909	/* Shortcut to ensure we don't store if no mlock. */
 910#if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
 911	return NT_STATUS_OK;
 912#else
 913	NTSTATUS status;
 914	struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
 915
 916	memcredp = find_memory_creds_by_name(username);
 917	if (uid == (uid_t)-1) {
 918		DEBUG(0,("winbindd_add_memory_creds_internal: "
 919			"invalid uid for user %s.\n", username));
 920		return NT_STATUS_INVALID_PARAMETER;
 921	}
 922
 923	if (memcredp) {
 924		/* Already exists. Increment the reference count and replace stored creds. */
 925		if (uid != memcredp->uid) {
 926			DEBUG(0,("winbindd_add_memory_creds_internal: "
 927				"uid %u for user %s doesn't "
 928				"match stored uid %u. Replacing.\n",
 929				(unsigned int)uid, username,
 930				(unsigned int)memcredp->uid));
 931			memcredp->uid = uid;
 932		}
 933		memcredp->ref_count++;
 934		DEBUG(10,("winbindd_add_memory_creds_internal: "
 935			"ref count for user %s is now %d\n",
 936			username, memcredp->ref_count));
 937		return winbindd_replace_memory_creds_internal(memcredp, pass);
 938	}
 939
 940	memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
 941	if (!memcredp) {
 942		return NT_STATUS_NO_MEMORY;
 943	}
 944	memcredp->username = talloc_strdup(memcredp, username);
 945	if (!memcredp->username) {
 946		talloc_destroy(memcredp);
 947		return NT_STATUS_NO_MEMORY;
 948	}
 949
 950	status = store_memory_creds(memcredp, pass);
 951	if (!NT_STATUS_IS_OK(status)) {
 952		talloc_destroy(memcredp);
 953		return status;
 954	}
 955
 956	memcredp->uid = uid;
 957	memcredp->ref_count = 1;
 958	DLIST_ADD(memory_creds_list, memcredp);
 959
 960	DEBUG(10,("winbindd_add_memory_creds_internal: "
 961		"added entry for user %s\n", username));
 962
 963	return NT_STATUS_OK;
 964#endif
 965}
 966
 967/*************************************************************
 968 Store users credentials in memory. If we also have a
 969 struct WINBINDD_CCACHE_ENTRY for this username with a
 970 refresh timer, then store the plaintext of the password
 971 and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
 972*************************************************************/
 973
 974NTSTATUS winbindd_add_memory_creds(const char *username,
 975				   uid_t uid,
 976				   const char *pass)
 977{
 978	struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
 979	NTSTATUS status;
 980
 981	status = winbindd_add_memory_creds_internal(username, uid, pass);
 982	if (!NT_STATUS_IS_OK(status)) {
 983		return status;
 984	}
 985
 986	if (entry) {
 987		struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
 988		memcredp = find_memory_creds_by_name(username);
 989		if (memcredp) {
 990			entry->cred_ptr = memcredp;
 991		}
 992	}
 993
 994	return status;
 995}
 996
 997/*************************************************************
 998 Decrement the in-memory ref count - delete if zero.
 999*************************************************************/
1000
1001NTSTATUS winbindd_delete_memory_creds(const char *username)
1002{
1003	struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1004	struct WINBINDD_CCACHE_ENTRY *entry = NULL;
1005	NTSTATUS status = NT_STATUS_OK;
1006
1007	memcredp = find_memory_creds_by_name(username);
1008	entry = get_ccache_by_username(username);
1009
1010	if (!memcredp) {
1011		DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
1012			username));
1013		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1014	}
1015
1016	if (memcredp->ref_count <= 0) {
1017		DEBUG(0,("winbindd_delete_memory_creds: logic error. "
1018			"ref count for user %s = %d\n",
1019			username, memcredp->ref_count));
1020		status = NT_STATUS_INTERNAL_DB_CORRUPTION;
1021	}
1022
1023	memcredp->ref_count--;
1024	if (memcredp->ref_count <= 0) {
1025		delete_memory_creds(memcredp);
1026		DLIST_REMOVE(memory_creds_list, memcredp);
1027		talloc_destroy(memcredp);
1028		DEBUG(10,("winbindd_delete_memory_creds: "
1029			"deleted entry for user %s\n",
1030			username));
1031	} else {
1032		DEBUG(10,("winbindd_delete_memory_creds: "
1033			"entry for user %s ref_count now %d\n",
1034			username, memcredp->ref_count));
1035	}
1036
1037	if (entry) {
1038		/* Ensure we have no dangling references to this. */
1039		entry->cred_ptr = NULL;
1040	}
1041
1042	return status;
1043}
1044
1045/***********************************************************
1046 Replace the required creds with new ones (password change).
1047***********************************************************/
1048
1049NTSTATUS winbindd_replace_memory_creds(const char *username,
1050				       const char *pass)
1051{
1052	struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1053
1054	memcredp = find_memory_creds_by_name(username);
1055	if (!memcredp) {
1056		DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
1057			username));
1058		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1059	}
1060
1061	DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
1062		username));
1063
1064	return winbindd_replace_memory_creds_internal(memcredp, pass);
1065}