Add comment explaining the previous fix.
[Samba.git] / source / winbindd / winbindd_cred_cache.c
blobb867641ef1e0e954e3290699fc804ea9812a68c1
1 /*
2 Unix SMB/CIFS implementation.
4 Winbind daemon - krb5 credential cache functions
5 and in-memory cache functions.
7 Copyright (C) Guenther Deschner 2005-2006
8 Copyright (C) Jeremy Allison 2006
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.
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.
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/>.
24 #include "includes.h"
25 #include "winbindd.h"
26 #undef DBGC_CLASS
27 #define DBGC_CLASS DBGC_WINBIND
29 /* uncomment this to do fast debugging on the krb5 ticket renewal event */
30 #ifdef DEBUG_KRB5_TKT_RENEWAL
31 #undef DEBUG_KRB5_TKT_RENEWAL
32 #endif
34 #define MAX_CCACHES 100
36 static struct WINBINDD_CCACHE_ENTRY *ccache_list;
37 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
38 struct timeval t);
40 /* The Krb5 ticket refresh handler should be scheduled
41 at one-half of the period from now till the tkt
42 expiration */
43 #define KRB5_EVENT_REFRESH_TIME(x) ((x) - (((x) - time(NULL))/2))
45 /****************************************************************
46 Find an entry by name.
47 ****************************************************************/
49 static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
51 struct WINBINDD_CCACHE_ENTRY *entry;
53 for (entry = ccache_list; entry; entry = entry->next) {
54 if (strequal(entry->username, username)) {
55 return entry;
58 return NULL;
61 /****************************************************************
62 How many do we have ?
63 ****************************************************************/
65 static int ccache_entry_count(void)
67 struct WINBINDD_CCACHE_ENTRY *entry;
68 int i = 0;
70 for (entry = ccache_list; entry; entry = entry->next) {
71 i++;
73 return i;
76 void ccache_remove_all_after_fork(void)
78 struct WINBINDD_CCACHE_ENTRY *cur;
79 cur = ccache_list;
80 while (cur) {
81 DLIST_REMOVE(ccache_list, cur);
82 TALLOC_FREE(cur->event);
83 TALLOC_FREE(cur);
84 cur = ccache_list;
88 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
89 struct timed_event *te,
90 const struct timeval *now,
91 void *private_data);
92 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
93 struct timed_event *te,
94 const struct timeval *now,
95 void *private_data);
97 void ccache_regain_all_now(void)
99 struct WINBINDD_CCACHE_ENTRY *cur;
100 struct timeval t = timeval_current();
102 for (cur = ccache_list; cur; cur = cur->next) {
103 struct timed_event *new_event;
106 * if refresh_time is 0, we know that the
107 * the event has the krb5_ticket_gain_handler
109 if (cur->refresh_time == 0) {
110 new_event = event_add_timed(winbind_event_context(),
111 cur, t,
112 "krb5_ticket_gain_handler",
113 krb5_ticket_gain_handler,
114 cur);
115 } else {
116 new_event = event_add_timed(winbind_event_context(),
117 cur, t,
118 "krb5_ticket_refresh_handler",
119 krb5_ticket_refresh_handler,
120 cur);
122 if (!new_event) {
123 continue;
126 TALLOC_FREE(cur->event);
127 cur->event = new_event;
129 return;
132 /****************************************************************
133 The gain initial tikcet case is recongnized as entry->refresh_time
134 is always zero.
135 ****************************************************************/
137 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
138 struct timeval t)
140 entry->refresh_time = 0;
141 entry->event = event_add_timed(winbind_event_context(), entry,
143 "krb5_ticket_gain_handler",
144 krb5_ticket_gain_handler,
145 entry);
149 /****************************************************************
150 Do the work of refreshing the ticket.
151 ****************************************************************/
153 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
154 struct timed_event *te,
155 const struct timeval *now,
156 void *private_data)
158 struct WINBINDD_CCACHE_ENTRY *entry =
159 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
160 #ifdef HAVE_KRB5
161 int ret;
162 time_t new_start;
163 time_t expire_time = 0;
164 struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
165 #endif
167 DEBUG(10,("krb5_ticket_refresh_handler called\n"));
168 DEBUGADD(10,("event called for: %s, %s\n",
169 entry->ccname, entry->username));
171 TALLOC_FREE(entry->event);
173 #ifdef HAVE_KRB5
175 /* Kinit again if we have the user password and we can't renew the old
176 * tgt anymore
177 * NB
178 * This happens when machine are put to sleep for a very long time. */
180 if (entry->renew_until < time(NULL)) {
181 rekinit:
182 if (cred_ptr && cred_ptr->pass) {
184 set_effective_uid(entry->uid);
186 ret = kerberos_kinit_password_ext(entry->principal_name,
187 cred_ptr->pass,
188 0, /* hm, can we do time correction here ? */
189 &entry->refresh_time,
190 &entry->renew_until,
191 entry->ccname,
192 False, /* no PAC required anymore */
193 True,
194 WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
195 NULL);
196 gain_root_privilege();
198 if (ret) {
199 DEBUG(3,("krb5_ticket_refresh_handler: "
200 "could not re-kinit: %s\n",
201 error_message(ret)));
202 /* destroy the ticket because we cannot rekinit
203 * it, ignore error here */
204 ads_kdestroy(entry->ccname);
206 /* Don't break the ticket refresh chain: retry
207 * refreshing ticket sometime later when KDC is
208 * unreachable -- BoYang
209 * */
211 if ((ret == KRB5_KDC_UNREACH)
212 || (ret == KRB5_REALM_CANT_RESOLVE)) {
213 #if defined(DEBUG_KRB5_TKT_RENEWAL)
214 new_start = time(NULL) + 30;
215 #else
216 new_start = time(NULL) +
217 MAX(30, lp_winbind_cache_time());
218 #endif
219 /* try to regain ticket here */
220 add_krb5_ticket_gain_handler_event(entry,
221 timeval_set(new_start, 0));
222 return;
224 TALLOC_FREE(entry->event);
225 return;
228 DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
229 "for: %s in ccache: %s\n",
230 entry->principal_name, entry->ccname));
232 #if defined(DEBUG_KRB5_TKT_RENEWAL)
233 new_start = time(NULL) + 30;
234 #else
235 /* The tkt should be refreshed at one-half the period
236 from now to the expiration time */
237 expire_time = entry->refresh_time;
238 new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
239 #endif
240 goto done;
241 } else {
242 /* can this happen?
243 * No cached credentials
244 * destroy ticket and refresh chain
245 * */
246 ads_kdestroy(entry->ccname);
247 TALLOC_FREE(entry->event);
248 return;
252 set_effective_uid(entry->uid);
254 ret = smb_krb5_renew_ticket(entry->ccname,
255 entry->principal_name,
256 entry->service,
257 &new_start);
258 #if defined(DEBUG_KRB5_TKT_RENEWAL)
259 new_start = time(NULL) + 30;
260 #else
261 expire_time = new_start;
262 new_start = KRB5_EVENT_REFRESH_TIME(new_start);
263 #endif
265 gain_root_privilege();
267 if (ret) {
268 DEBUG(3,("krb5_ticket_refresh_handler: "
269 "could not renew tickets: %s\n",
270 error_message(ret)));
271 /* maybe we are beyond the renewing window */
273 /* evil rises here, we refresh ticket failed,
274 * but the ticket might be expired. Therefore,
275 * When we refresh ticket failed, destory the
276 * ticket */
278 ads_kdestroy(entry->ccname);
280 /* avoid breaking the renewal chain: retry in
281 * lp_winbind_cache_time() seconds when the KDC was not
282 * available right now.
283 * the return code can be KRB5_REALM_CANT_RESOLVE*/
285 if ((ret == KRB5_KDC_UNREACH)
286 || (ret == KRB5_REALM_CANT_RESOLVE)) {
287 #if defined(DEBUG_KRB5_TKT_RENEWAL)
288 new_start = time(NULL) + 30;
289 #else
290 new_start = time(NULL) +
291 MAX(30, lp_winbind_cache_time());
292 #endif
293 /* ticket is destroyed here, we have to regain it
294 * if it is possible */
295 add_krb5_ticket_gain_handler_event(entry,
296 timeval_set(new_start, 0));
297 return;
300 /* This is evil, if the ticket was already expired.
301 * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
302 * But there is still a chance that we can rekinit it.
304 * This happens when user login in online mode, and then network
305 * down or something cause winbind goes offline for a very long time,
306 * and then goes online again. ticket expired, renew failed.
307 * This happens when machine are put to sleep for a long time,
308 * but shorter than entry->renew_util.
309 * NB
310 * Looks like the KDC is reachable, we want to rekinit as soon as
311 * possible instead of waiting some time later. */
312 if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
313 || (ret == KRB5_FCC_NOFILE)) goto rekinit;
315 return;
318 done:
319 /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
320 * but try to regain ticket if it is possible */
321 if (entry->renew_until && expire_time
322 && (entry->renew_until <= expire_time)) {
323 /* try to regain ticket 10 seconds beforre expiration */
324 expire_time -= 10;
325 add_krb5_ticket_gain_handler_event(entry, timeval_set(expire_time, 0));
326 return;
329 if (!entry->refresh_time) {
330 entry->refresh_time = new_start;
332 entry->event = event_add_timed(winbind_event_context(), entry,
333 timeval_set(new_start, 0),
334 "krb5_ticket_refresh_handler",
335 krb5_ticket_refresh_handler,
336 entry);
338 #endif
341 /****************************************************************
342 Do the work of regaining a ticket when coming from offline auth.
343 ****************************************************************/
345 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
346 struct timed_event *te,
347 const struct timeval *now,
348 void *private_data)
350 struct WINBINDD_CCACHE_ENTRY *entry =
351 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
352 #ifdef HAVE_KRB5
353 int ret;
354 struct timeval t;
355 struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
356 struct winbindd_domain *domain = NULL;
357 #endif
359 DEBUG(10,("krb5_ticket_gain_handler called\n"));
360 DEBUGADD(10,("event called for: %s, %s\n",
361 entry->ccname, entry->username));
363 TALLOC_FREE(entry->event);
365 #ifdef HAVE_KRB5
367 if (!cred_ptr || !cred_ptr->pass) {
368 DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
369 return;
372 if ((domain = find_domain_from_name(entry->realm)) == NULL) {
373 DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
374 return;
377 if (!domain->online) {
378 goto retry_later;
381 set_effective_uid(entry->uid);
383 ret = kerberos_kinit_password_ext(entry->principal_name,
384 cred_ptr->pass,
385 0, /* hm, can we do time correction here ? */
386 &entry->refresh_time,
387 &entry->renew_until,
388 entry->ccname,
389 False, /* no PAC required anymore */
390 True,
391 WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
392 NULL);
393 gain_root_privilege();
395 if (ret) {
396 DEBUG(3,("krb5_ticket_gain_handler: "
397 "could not kinit: %s\n",
398 error_message(ret)));
399 /* evil. If we cannot do it, destroy any the __maybe__
400 * __existing__ ticket */
401 ads_kdestroy(entry->ccname);
402 goto retry_later;
405 DEBUG(10,("krb5_ticket_gain_handler: "
406 "successful kinit for: %s in ccache: %s\n",
407 entry->principal_name, entry->ccname));
409 goto got_ticket;
411 retry_later:
413 #if defined(DEBUG_KRB5_TKT_REGAIN)
414 t = timeval_set(time(NULL) + 30, 0);
415 #else
416 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
417 #endif
419 add_krb5_ticket_gain_handler_event(entry, t);
421 return;
423 got_ticket:
425 #if defined(DEBUG_KRB5_TKT_RENEWAL)
426 t = timeval_set(time(NULL) + 30, 0);
427 #else
428 t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
429 #endif
431 if (!entry->refresh_time) {
432 entry->refresh_time = t.tv_sec;
434 entry->event = event_add_timed(winbind_event_context(),
435 entry,
437 "krb5_ticket_refresh_handler",
438 krb5_ticket_refresh_handler,
439 entry);
441 return;
442 #endif
445 /****************************************************************
446 Check if an ccache entry exists.
447 ****************************************************************/
449 bool ccache_entry_exists(const char *username)
451 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
452 return (entry != NULL);
455 /****************************************************************
456 Ensure we're changing the correct entry.
457 ****************************************************************/
459 bool ccache_entry_identical(const char *username,
460 uid_t uid,
461 const char *ccname)
463 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
465 if (!entry) {
466 return False;
469 if (entry->uid != uid) {
470 DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
471 (unsigned int)entry->uid, (unsigned int)uid));
472 return False;
474 if (!strcsequal(entry->ccname, ccname)) {
475 DEBUG(0,("cache_entry_identical: "
476 "ccnames differ: (cache) %s != (client) %s\n",
477 entry->ccname, ccname));
478 return False;
480 return True;
483 NTSTATUS add_ccache_to_list(const char *princ_name,
484 const char *ccname,
485 const char *service,
486 const char *username,
487 const char *realm,
488 uid_t uid,
489 time_t create_time,
490 time_t ticket_end,
491 time_t renew_until,
492 bool postponed_request)
494 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
495 struct timeval t;
496 NTSTATUS ntret;
497 #ifdef HAVE_KRB5
498 int ret;
499 #endif
501 if ((username == NULL && princ_name == NULL) ||
502 ccname == NULL || uid < 0) {
503 return NT_STATUS_INVALID_PARAMETER;
506 if (ccache_entry_count() + 1 > MAX_CCACHES) {
507 DEBUG(10,("add_ccache_to_list: "
508 "max number of ccaches reached\n"));
509 return NT_STATUS_NO_MORE_ENTRIES;
512 /* If it is cached login, destroy krb5 ticket
513 * to avoid surprise. */
514 #ifdef HAVE_KRB5
515 if (postponed_request) {
516 /* ignore KRB5_FCC_NOFILE error here */
517 ret = ads_kdestroy(ccname);
518 if (ret == KRB5_FCC_NOFILE) {
519 ret = 0;
521 if (ret) {
522 DEBUG(0, ("add_ccache_to_list: failed to destroy "
523 "user krb5 ccache %s with %s\n", ccname,
524 error_message(ret)));
525 return krb5_to_nt_status(ret);
526 } else {
527 DEBUG(10, ("add_ccache_to_list: successfully destroyed "
528 "krb5 ccache %s for user %s\n", ccname,
529 username));
532 #endif
534 /* Reference count old entries */
535 entry = get_ccache_by_username(username);
536 if (entry) {
537 /* Check cached entries are identical. */
538 if (!ccache_entry_identical(username, uid, ccname)) {
539 return NT_STATUS_INVALID_PARAMETER;
541 entry->ref_count++;
542 DEBUG(10,("add_ccache_to_list: "
543 "ref count on entry %s is now %d\n",
544 username, entry->ref_count));
545 /* FIXME: in this case we still might want to have a krb5 cred
546 * event handler created - gd
547 * Add ticket refresh handler here */
549 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
550 return NT_STATUS_OK;
553 if (!entry->event) {
554 if (postponed_request) {
555 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
556 add_krb5_ticket_gain_handler_event(entry, t);
557 } else {
558 /* Renew at 1/2 the ticket expiration time */
559 #if defined(DEBUG_KRB5_TKT_RENEWAL)
560 t = timeval_set(time(NULL)+30, 0);
561 #else
562 t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
563 #endif
564 if (!entry->refresh_time) {
565 entry->refresh_time = t.tv_sec;
567 entry->event = event_add_timed(winbind_event_context(),
568 entry,
570 "krb5_ticket_refresh_handler",
571 krb5_ticket_refresh_handler,
572 entry);
575 if (!entry->event) {
576 ntret = remove_ccache(username);
577 if (!NT_STATUS_IS_OK(ntret)) {
578 DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
579 "ccache %s for user %s\n", entry->ccname,
580 entry->username));
581 DEBUG(0, ("add_ccache_to_list: error is %s\n",
582 nt_errstr(ntret)));
583 return ntret;
585 return NT_STATUS_NO_MEMORY;
588 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
591 return NT_STATUS_OK;
594 entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
595 if (!entry) {
596 return NT_STATUS_NO_MEMORY;
599 ZERO_STRUCTP(entry);
601 if (username) {
602 entry->username = talloc_strdup(entry, username);
603 if (!entry->username) {
604 goto no_mem;
607 if (princ_name) {
608 entry->principal_name = talloc_strdup(entry, princ_name);
609 if (!entry->principal_name) {
610 goto no_mem;
613 if (service) {
614 entry->service = talloc_strdup(entry, service);
615 if (!entry->service) {
616 goto no_mem;
620 entry->ccname = talloc_strdup(entry, ccname);
621 if (!entry->ccname) {
622 goto no_mem;
625 entry->realm = talloc_strdup(entry, realm);
626 if (!entry->realm) {
627 goto no_mem;
630 entry->create_time = create_time;
631 entry->renew_until = renew_until;
632 entry->uid = uid;
633 entry->ref_count = 1;
635 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
636 goto add_entry;
639 if (postponed_request) {
640 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
641 add_krb5_ticket_gain_handler_event(entry, t);
642 } else {
643 /* Renew at 1/2 the ticket expiration time */
644 #if defined(DEBUG_KRB5_TKT_RENEWAL)
645 t = timeval_set(time(NULL)+30, 0);
646 #else
647 t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
648 #endif
649 if (!entry->refresh_time) {
650 entry->refresh_time = t.tv_sec;
653 entry->event = event_add_timed(winbind_event_context(),
654 entry,
656 "krb5_ticket_refresh_handler",
657 krb5_ticket_refresh_handler,
658 entry);
661 if (!entry->event) {
662 goto no_mem;
665 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
667 add_entry:
669 DLIST_ADD(ccache_list, entry);
671 DEBUG(10,("add_ccache_to_list: "
672 "added ccache [%s] for user [%s] to the list\n",
673 ccname, username));
675 return NT_STATUS_OK;
677 no_mem:
679 TALLOC_FREE(entry);
680 return NT_STATUS_NO_MEMORY;
683 /*******************************************************************
684 Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
685 referenced.
686 *******************************************************************/
688 NTSTATUS remove_ccache(const char *username)
690 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
691 NTSTATUS status = NT_STATUS_OK;
692 #ifdef HAVE_KRB5
693 krb5_error_code ret;
694 #endif
696 if (!entry) {
697 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
700 if (entry->ref_count <= 0) {
701 DEBUG(0,("remove_ccache: logic error. "
702 "ref count for user %s = %d\n",
703 username, entry->ref_count));
704 return NT_STATUS_INTERNAL_DB_CORRUPTION;
707 entry->ref_count--;
709 if (entry->ref_count > 0) {
710 DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
711 username, entry->ref_count));
712 return NT_STATUS_OK;
715 /* no references any more */
717 DLIST_REMOVE(ccache_list, entry);
718 TALLOC_FREE(entry->event); /* unregisters events */
720 #ifdef HAVE_KRB5
721 ret = ads_kdestroy(entry->ccname);
723 /* we ignore the error when there has been no credential cache */
724 if (ret == KRB5_FCC_NOFILE) {
725 ret = 0;
726 } else if (ret) {
727 DEBUG(0,("remove_ccache: "
728 "failed to destroy user krb5 ccache %s with: %s\n",
729 entry->ccname, error_message(ret)));
730 } else {
731 DEBUG(10,("remove_ccache: "
732 "successfully destroyed krb5 ccache %s for user %s\n",
733 entry->ccname, username));
735 status = krb5_to_nt_status(ret);
736 #endif
738 TALLOC_FREE(entry);
739 DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
741 return status;
744 /*******************************************************************
745 In memory credentials cache code.
746 *******************************************************************/
748 static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
750 /***********************************************************
751 Find an entry on the list by name.
752 ***********************************************************/
754 struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
756 struct WINBINDD_MEMORY_CREDS *p;
758 for (p = memory_creds_list; p; p = p->next) {
759 if (strequal(p->username, username)) {
760 return p;
763 return NULL;
766 /***********************************************************
767 Store the required creds and mlock them.
768 ***********************************************************/
770 static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
771 const char *pass)
773 #if !defined(HAVE_MLOCK)
774 return NT_STATUS_OK;
775 #else
776 /* new_entry->nt_hash is the base pointer for the block
777 of memory pointed into by new_entry->lm_hash and
778 new_entry->pass (if we're storing plaintext). */
780 memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
781 if (pass) {
782 memcredp->len += strlen(pass)+1;
786 #if defined(LINUX)
787 /* aligning the memory on on x86_64 and compiling
788 with gcc 4.1 using -O2 causes a segv in the
789 next memset() --jerry */
790 memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
791 #else
792 /* On non-linux platforms, mlock()'d memory must be aligned */
793 memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
794 getpagesize(), memcredp->len);
795 #endif
796 if (!memcredp->nt_hash) {
797 return NT_STATUS_NO_MEMORY;
799 memset(memcredp->nt_hash, 0x0, memcredp->len);
801 memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
803 #ifdef DEBUG_PASSWORD
804 DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
805 #endif
806 if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
807 DEBUG(0,("failed to mlock memory: %s (%d)\n",
808 strerror(errno), errno));
809 SAFE_FREE(memcredp->nt_hash);
810 return map_nt_error_from_unix(errno);
813 #ifdef DEBUG_PASSWORD
814 DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
815 #endif
817 /* Create and store the password hashes. */
818 E_md4hash(pass, memcredp->nt_hash);
819 E_deshash(pass, memcredp->lm_hash);
821 if (pass) {
822 memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
823 memcpy(memcredp->pass, pass,
824 memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
827 return NT_STATUS_OK;
828 #endif
831 /***********************************************************
832 Destroy existing creds.
833 ***********************************************************/
835 static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
837 #if !defined(HAVE_MUNLOCK)
838 return NT_STATUS_OK;
839 #else
840 if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
841 DEBUG(0,("failed to munlock memory: %s (%d)\n",
842 strerror(errno), errno));
843 return map_nt_error_from_unix(errno);
845 memset(memcredp->nt_hash, '\0', memcredp->len);
846 SAFE_FREE(memcredp->nt_hash);
847 memcredp->nt_hash = NULL;
848 memcredp->lm_hash = NULL;
849 memcredp->pass = NULL;
850 memcredp->len = 0;
851 return NT_STATUS_OK;
852 #endif
855 /***********************************************************
856 Replace the required creds with new ones (password change).
857 ***********************************************************/
859 static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
860 const char *pass)
862 NTSTATUS status = delete_memory_creds(memcredp);
863 if (!NT_STATUS_IS_OK(status)) {
864 return status;
866 return store_memory_creds(memcredp, pass);
869 /*************************************************************
870 Store credentials in memory in a list.
871 *************************************************************/
873 static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
874 uid_t uid,
875 const char *pass)
877 /* Shortcut to ensure we don't store if no mlock. */
878 #if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
879 return NT_STATUS_OK;
880 #else
881 NTSTATUS status;
882 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
884 memcredp = find_memory_creds_by_name(username);
885 if (uid == (uid_t)-1) {
886 DEBUG(0,("winbindd_add_memory_creds_internal: "
887 "invalid uid for user %s.\n", username));
888 return NT_STATUS_INVALID_PARAMETER;
891 if (memcredp) {
892 /* Already exists. Increment the reference count and replace stored creds. */
893 if (uid != memcredp->uid) {
894 DEBUG(0,("winbindd_add_memory_creds_internal: "
895 "uid %u for user %s doesn't "
896 "match stored uid %u. Replacing.\n",
897 (unsigned int)uid, username,
898 (unsigned int)memcredp->uid));
899 memcredp->uid = uid;
901 memcredp->ref_count++;
902 DEBUG(10,("winbindd_add_memory_creds_internal: "
903 "ref count for user %s is now %d\n",
904 username, memcredp->ref_count));
905 return winbindd_replace_memory_creds_internal(memcredp, pass);
908 memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
909 if (!memcredp) {
910 return NT_STATUS_NO_MEMORY;
912 memcredp->username = talloc_strdup(memcredp, username);
913 if (!memcredp->username) {
914 talloc_destroy(memcredp);
915 return NT_STATUS_NO_MEMORY;
918 status = store_memory_creds(memcredp, pass);
919 if (!NT_STATUS_IS_OK(status)) {
920 talloc_destroy(memcredp);
921 return status;
924 memcredp->uid = uid;
925 memcredp->ref_count = 1;
926 DLIST_ADD(memory_creds_list, memcredp);
928 DEBUG(10,("winbindd_add_memory_creds_internal: "
929 "added entry for user %s\n", username));
931 return NT_STATUS_OK;
932 #endif
935 /*************************************************************
936 Store users credentials in memory. If we also have a
937 struct WINBINDD_CCACHE_ENTRY for this username with a
938 refresh timer, then store the plaintext of the password
939 and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
940 *************************************************************/
942 NTSTATUS winbindd_add_memory_creds(const char *username,
943 uid_t uid,
944 const char *pass)
946 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
947 NTSTATUS status;
949 status = winbindd_add_memory_creds_internal(username, uid, pass);
950 if (!NT_STATUS_IS_OK(status)) {
951 return status;
954 if (entry) {
955 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
956 memcredp = find_memory_creds_by_name(username);
957 if (memcredp) {
958 entry->cred_ptr = memcredp;
962 return status;
965 /*************************************************************
966 Decrement the in-memory ref count - delete if zero.
967 *************************************************************/
969 NTSTATUS winbindd_delete_memory_creds(const char *username)
971 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
972 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
973 NTSTATUS status = NT_STATUS_OK;
975 memcredp = find_memory_creds_by_name(username);
976 entry = get_ccache_by_username(username);
978 if (!memcredp) {
979 DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
980 username));
981 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
984 if (memcredp->ref_count <= 0) {
985 DEBUG(0,("winbindd_delete_memory_creds: logic error. "
986 "ref count for user %s = %d\n",
987 username, memcredp->ref_count));
988 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
991 memcredp->ref_count--;
992 if (memcredp->ref_count <= 0) {
993 delete_memory_creds(memcredp);
994 DLIST_REMOVE(memory_creds_list, memcredp);
995 talloc_destroy(memcredp);
996 DEBUG(10,("winbindd_delete_memory_creds: "
997 "deleted entry for user %s\n",
998 username));
999 } else {
1000 DEBUG(10,("winbindd_delete_memory_creds: "
1001 "entry for user %s ref_count now %d\n",
1002 username, memcredp->ref_count));
1005 if (entry) {
1006 /* Ensure we have no dangling references to this. */
1007 entry->cred_ptr = NULL;
1010 return status;
1013 /***********************************************************
1014 Replace the required creds with new ones (password change).
1015 ***********************************************************/
1017 NTSTATUS winbindd_replace_memory_creds(const char *username,
1018 const char *pass)
1020 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1022 memcredp = find_memory_creds_by_name(username);
1023 if (!memcredp) {
1024 DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
1025 username));
1026 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1029 DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
1030 username));
1032 return winbindd_replace_memory_creds_internal(memcredp, pass);