Factor out lots of common code into a function.
[Samba/ekacnet.git] / source3 / winbindd / winbindd_cred_cache.c
blobff6d0f3df76ecc2c7a0ae6571b752f0b1ae94cad
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 krb5_ticket_gain_handler(struct event_context *,
38 struct timed_event *,
39 struct timeval,
40 void *);
41 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
42 struct timeval);
44 /* The Krb5 ticket refresh handler should be scheduled
45 at one-half of the period from now till the tkt
46 expiration */
47 #define KRB5_EVENT_REFRESH_TIME(x) ((x) - (((x) - time(NULL))/2))
49 /****************************************************************
50 Find an entry by name.
51 ****************************************************************/
53 static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
55 struct WINBINDD_CCACHE_ENTRY *entry;
57 for (entry = ccache_list; entry; entry = entry->next) {
58 if (strequal(entry->username, username)) {
59 return entry;
62 return NULL;
65 /****************************************************************
66 How many do we have ?
67 ****************************************************************/
69 static int ccache_entry_count(void)
71 struct WINBINDD_CCACHE_ENTRY *entry;
72 int i = 0;
74 for (entry = ccache_list; entry; entry = entry->next) {
75 i++;
77 return i;
80 void ccache_remove_all_after_fork(void)
82 struct WINBINDD_CCACHE_ENTRY *cur, *next;
84 for (cur = ccache_list; cur; cur = next) {
85 next = cur->next;
86 DLIST_REMOVE(ccache_list, cur);
87 TALLOC_FREE(cur->event);
88 TALLOC_FREE(cur);
91 return;
94 /****************************************************************
95 Do the work of refreshing the ticket.
96 ****************************************************************/
98 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
99 struct timed_event *te,
100 struct timeval now,
101 void *private_data)
103 struct WINBINDD_CCACHE_ENTRY *entry =
104 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
105 #ifdef HAVE_KRB5
106 int ret;
107 time_t new_start;
108 time_t expire_time = 0;
109 struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
110 #endif
112 DEBUG(10,("krb5_ticket_refresh_handler called\n"));
113 DEBUGADD(10,("event called for: %s, %s\n",
114 entry->ccname, entry->username));
116 TALLOC_FREE(entry->event);
118 #ifdef HAVE_KRB5
120 /* Kinit again if we have the user password and we can't renew the old
121 * tgt anymore
122 * NB
123 * This happens when machine are put to sleep for a very long time. */
125 if (entry->renew_until < time(NULL)) {
126 rekinit:
127 if (cred_ptr && cred_ptr->pass) {
129 set_effective_uid(entry->uid);
131 ret = kerberos_kinit_password_ext(entry->principal_name,
132 cred_ptr->pass,
133 0, /* hm, can we do time correction here ? */
134 &entry->refresh_time,
135 &entry->renew_until,
136 entry->ccname,
137 False, /* no PAC required anymore */
138 True,
139 WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
140 NULL);
141 gain_root_privilege();
143 if (ret) {
144 DEBUG(3,("krb5_ticket_refresh_handler: "
145 "could not re-kinit: %s\n",
146 error_message(ret)));
147 /* destroy the ticket because we cannot rekinit
148 * it, ignore error here */
149 ads_kdestroy(entry->ccname);
151 /* Don't break the ticket refresh chain: retry
152 * refreshing ticket sometime later when KDC is
153 * unreachable -- BoYang. More error code handling
154 * here?
155 * */
157 if ((ret == KRB5_KDC_UNREACH)
158 || (ret == KRB5_REALM_CANT_RESOLVE)) {
159 #if defined(DEBUG_KRB5_TKT_RENEWAL)
160 new_start = time(NULL) + 30;
161 #else
162 new_start = time(NULL) +
163 MAX(30, lp_winbind_cache_time());
164 #endif
165 add_krb5_ticket_gain_handler_event(entry,
166 timeval_set(new_start, 0));
167 return;
169 TALLOC_FREE(entry->event);
170 return;
173 DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
174 "for: %s in ccache: %s\n",
175 entry->principal_name, entry->ccname));
177 #if defined(DEBUG_KRB5_TKT_RENEWAL)
178 new_start = time(NULL) + 30;
179 #else
180 /* The tkt should be refreshed at one-half the period
181 from now to the expiration time */
182 expire_time = entry->refresh_time;
183 new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
184 #endif
185 goto done;
186 } else {
187 /* can this happen?
188 * No cached credentials
189 * destroy ticket and refresh chain
190 * */
191 ads_kdestroy(entry->ccname);
192 TALLOC_FREE(entry->event);
193 return;
197 set_effective_uid(entry->uid);
199 ret = smb_krb5_renew_ticket(entry->ccname,
200 entry->principal_name,
201 entry->service,
202 &new_start);
203 #if defined(DEBUG_KRB5_TKT_RENEWAL)
204 new_start = time(NULL) + 30;
205 #else
206 expire_time = new_start;
207 new_start = KRB5_EVENT_REFRESH_TIME(new_start);
208 #endif
210 gain_root_privilege();
212 if (ret) {
213 DEBUG(3,("krb5_ticket_refresh_handler: "
214 "could not renew tickets: %s\n",
215 error_message(ret)));
216 /* maybe we are beyond the renewing window */
218 /* evil rises here, we refresh ticket failed,
219 * but the ticket might be expired. Therefore,
220 * When we refresh ticket failed, destory the
221 * ticket */
223 ads_kdestroy(entry->ccname);
225 /* avoid breaking the renewal chain: retry in
226 * lp_winbind_cache_time() seconds when the KDC was not
227 * available right now.
228 * the return code can be KRB5_REALM_CANT_RESOLVE.
229 * More error code handling here? */
231 if ((ret == KRB5_KDC_UNREACH)
232 || (ret == KRB5_REALM_CANT_RESOLVE)) {
233 #if defined(DEBUG_KRB5_TKT_RENEWAL)
234 new_start = time(NULL) + 30;
235 #else
236 new_start = time(NULL) +
237 MAX(30, lp_winbind_cache_time());
238 #endif
239 /* ticket is destroyed here, we have to regain it
240 * if it is possible */
241 add_krb5_ticket_gain_handler_event(entry,
242 timeval_set(new_start, 0));
243 return;
246 /* This is evil, if the ticket was already expired.
247 * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
248 * But there is still a chance that we can rekinit it.
250 * This happens when user login in online mode, and then network
251 * down or something cause winbind goes offline for a very long time,
252 * and then goes online again. ticket expired, renew failed.
253 * This happens when machine are put to sleep for a long time,
254 * but shorter than entry->renew_util.
255 * NB
256 * Looks like the KDC is reachable, we want to rekinit as soon as
257 * possible instead of waiting some time later. */
258 if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
259 || (ret == KRB5_FCC_NOFILE)) goto rekinit;
261 return;
264 done:
265 /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
266 * but try to regain ticket if it is possible */
267 if (entry->renew_until && expire_time
268 && (entry->renew_until <= expire_time)) {
269 /* try to regain ticket 10 seconds beforre expiration */
270 expire_time -= 10;
271 add_krb5_ticket_gain_handler_event(entry,
272 timeval_set(expire_time, 0));
273 return;
276 if (entry->refresh_time == 0) {
277 entry->refresh_time = new_start;
279 entry->event = event_add_timed(winbind_event_context(), entry,
280 timeval_set(new_start, 0),
281 krb5_ticket_refresh_handler,
282 entry);
284 #endif
287 /****************************************************************
288 Do the work of regaining a ticket when coming from offline auth.
289 ****************************************************************/
291 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
292 struct timed_event *te,
293 struct timeval now,
294 void *private_data)
296 struct WINBINDD_CCACHE_ENTRY *entry =
297 talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
298 #ifdef HAVE_KRB5
299 int ret;
300 struct timeval t;
301 struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
302 struct winbindd_domain *domain = NULL;
303 #endif
305 DEBUG(10,("krb5_ticket_gain_handler called\n"));
306 DEBUGADD(10,("event called for: %s, %s\n",
307 entry->ccname, entry->username));
309 TALLOC_FREE(entry->event);
311 #ifdef HAVE_KRB5
313 if (!cred_ptr || !cred_ptr->pass) {
314 DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
315 return;
318 if ((domain = find_domain_from_name(entry->realm)) == NULL) {
319 DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
320 return;
323 if (!domain->online) {
324 goto retry_later;
327 set_effective_uid(entry->uid);
329 ret = kerberos_kinit_password_ext(entry->principal_name,
330 cred_ptr->pass,
331 0, /* hm, can we do time correction here ? */
332 &entry->refresh_time,
333 &entry->renew_until,
334 entry->ccname,
335 False, /* no PAC required anymore */
336 True,
337 WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
338 NULL);
339 gain_root_privilege();
341 if (ret) {
342 DEBUG(3,("krb5_ticket_gain_handler: "
343 "could not kinit: %s\n",
344 error_message(ret)));
345 /* evil. If we cannot do it, destroy any the __maybe__
346 * __existing__ ticket */
347 ads_kdestroy(entry->ccname);
348 goto retry_later;
351 DEBUG(10,("krb5_ticket_gain_handler: "
352 "successful kinit for: %s in ccache: %s\n",
353 entry->principal_name, entry->ccname));
355 goto got_ticket;
357 retry_later:
359 #if defined(DEBUG_KRB5_TKT_REGAIN)
360 t = timeval_set(time(NULL) + 30, 0);
361 #else
362 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
363 #endif
365 add_krb5_ticket_gain_handler_event(entry, t);
366 return;
368 got_ticket:
370 #if defined(DEBUG_KRB5_TKT_RENEWAL)
371 t = timeval_set(time(NULL) + 30, 0);
372 #else
373 t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
374 #endif
376 if (entry->refresh_time == 0) {
377 entry->refresh_time = t.tv_sec;
379 entry->event = event_add_timed(winbind_event_context(),
380 entry,
382 krb5_ticket_refresh_handler,
383 entry);
385 return;
386 #endif
389 /**************************************************************
390 The gain initial ticket case is recognised as entry->refresh_time
391 is always zero.
392 **************************************************************/
394 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
395 struct timeval t)
397 entry->refresh_time = 0;
398 entry->event = event_add_timed(winbind_event_context(),
399 entry,
401 krb5_ticket_gain_handler,
402 entry);
405 void ccache_regain_all_now(void)
407 struct WINBINDD_CCACHE_ENTRY *cur;
408 struct timeval t = timeval_current();
410 for (cur = ccache_list; cur; cur = cur->next) {
411 struct timed_event *new_event;
414 * if refresh_time is 0, we know that the
415 * the event has the krb5_ticket_gain_handler
417 if (cur->refresh_time == 0) {
418 new_event = event_add_timed(winbind_event_context(),
419 cur,
421 krb5_ticket_gain_handler,
422 cur);
423 } else {
424 new_event = event_add_timed(winbind_event_context(),
425 cur,
427 krb5_ticket_refresh_handler,
428 cur);
431 if (!new_event) {
432 continue;
435 TALLOC_FREE(cur->event);
436 cur->event = new_event;
439 return;
442 /****************************************************************
443 Check if an ccache entry exists.
444 ****************************************************************/
446 bool ccache_entry_exists(const char *username)
448 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
449 return (entry != NULL);
452 /****************************************************************
453 Ensure we're changing the correct entry.
454 ****************************************************************/
456 bool ccache_entry_identical(const char *username,
457 uid_t uid,
458 const char *ccname)
460 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
462 if (!entry) {
463 return False;
466 if (entry->uid != uid) {
467 DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
468 (unsigned int)entry->uid, (unsigned int)uid));
469 return False;
471 if (!strcsequal(entry->ccname, ccname)) {
472 DEBUG(0,("cache_entry_identical: "
473 "ccnames differ: (cache) %s != (client) %s\n",
474 entry->ccname, ccname));
475 return False;
477 return True;
480 NTSTATUS add_ccache_to_list(const char *princ_name,
481 const char *ccname,
482 const char *service,
483 const char *username,
484 const char *realm,
485 uid_t uid,
486 time_t create_time,
487 time_t ticket_end,
488 time_t renew_until,
489 bool postponed_request)
491 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
492 struct timeval t;
493 NTSTATUS ntret;
494 #ifdef HAVE_KRB5
495 int ret;
496 #endif
498 if ((username == NULL && princ_name == NULL) ||
499 ccname == NULL || uid < 0) {
500 return NT_STATUS_INVALID_PARAMETER;
503 if (ccache_entry_count() + 1 > MAX_CCACHES) {
504 DEBUG(10,("add_ccache_to_list: "
505 "max number of ccaches reached\n"));
506 return NT_STATUS_NO_MORE_ENTRIES;
509 /* If it is cached login, destroy krb5 ticket
510 * to avoid surprise. */
511 #ifdef HAVE_KRB5
512 if (postponed_request) {
513 /* ignore KRB5_FCC_NOFILE error here */
514 ret = ads_kdestroy(ccname);
515 if (ret == KRB5_FCC_NOFILE) {
516 ret = 0;
518 if (ret) {
519 DEBUG(0, ("add_ccache_to_list: failed to destroy "
520 "user krb5 ccache %s with %s\n", ccname,
521 error_message(ret)));
522 return krb5_to_nt_status(ret);
523 } else {
524 DEBUG(10, ("add_ccache_to_list: successfully destroyed "
525 "krb5 ccache %s for user %s\n", ccname,
526 username));
529 #endif
531 /* Reference count old entries */
532 entry = get_ccache_by_username(username);
533 if (entry) {
534 /* Check cached entries are identical. */
535 if (!ccache_entry_identical(username, uid, ccname)) {
536 return NT_STATUS_INVALID_PARAMETER;
538 entry->ref_count++;
539 DEBUG(10,("add_ccache_to_list: "
540 "ref count on entry %s is now %d\n",
541 username, entry->ref_count));
542 /* FIXME: in this case we still might want to have a krb5 cred
543 * event handler created - gd
544 * Add ticket refresh handler here */
546 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
547 return NT_STATUS_OK;
550 if (!entry->event) {
551 if (postponed_request) {
552 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
553 add_krb5_ticket_gain_handler_event(entry, t);
554 } else {
555 /* Renew at 1/2 the ticket expiration time */
556 #if defined(DEBUG_KRB5_TKT_RENEWAL)
557 t = timeval_set(time(NULL)+30, 0);
558 #else
559 t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
560 #endif
561 if (!entry->refresh_time) {
562 entry->refresh_time = t.tv_sec;
564 entry->event = event_add_timed(winbind_event_context(),
565 entry,
567 krb5_ticket_refresh_handler,
568 entry);
571 if (!entry->event) {
572 ntret = remove_ccache(username);
573 if (!NT_STATUS_IS_OK(ntret)) {
574 DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
575 "ccache %s for user %s\n", entry->ccname,
576 entry->username));
577 DEBUG(0, ("add_ccache_to_list: error is %s\n",
578 nt_errstr(ntret)));
579 return ntret;
581 return NT_STATUS_NO_MEMORY;
584 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
587 return NT_STATUS_OK;
590 entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
591 if (!entry) {
592 return NT_STATUS_NO_MEMORY;
595 ZERO_STRUCTP(entry);
597 if (username) {
598 entry->username = talloc_strdup(entry, username);
599 if (!entry->username) {
600 goto no_mem;
603 if (princ_name) {
604 entry->principal_name = talloc_strdup(entry, princ_name);
605 if (!entry->principal_name) {
606 goto no_mem;
609 if (service) {
610 entry->service = talloc_strdup(entry, service);
611 if (!entry->service) {
612 goto no_mem;
616 entry->ccname = talloc_strdup(entry, ccname);
617 if (!entry->ccname) {
618 goto no_mem;
621 entry->realm = talloc_strdup(entry, realm);
622 if (!entry->realm) {
623 goto no_mem;
626 entry->create_time = create_time;
627 entry->renew_until = renew_until;
628 entry->uid = uid;
629 entry->ref_count = 1;
631 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
632 goto add_entry;
635 if (postponed_request) {
636 t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
637 add_krb5_ticket_gain_handler_event(entry, t);
638 } else {
639 /* Renew at 1/2 the ticket expiration time */
640 #if defined(DEBUG_KRB5_TKT_RENEWAL)
641 t = timeval_set(time(NULL)+30, 0);
642 #else
643 t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
644 #endif
645 if (entry->refresh_time == 0) {
646 entry->refresh_time = t.tv_sec;
648 entry->event = event_add_timed(winbind_event_context(),
649 entry,
651 krb5_ticket_refresh_handler,
652 entry);
655 if (!entry->event) {
656 goto no_mem;
659 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
661 add_entry:
663 DLIST_ADD(ccache_list, entry);
665 DEBUG(10,("add_ccache_to_list: "
666 "added ccache [%s] for user [%s] to the list\n",
667 ccname, username));
669 return NT_STATUS_OK;
671 no_mem:
673 TALLOC_FREE(entry);
674 return NT_STATUS_NO_MEMORY;
677 /*******************************************************************
678 Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
679 referenced.
680 *******************************************************************/
682 NTSTATUS remove_ccache(const char *username)
684 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
685 NTSTATUS status = NT_STATUS_OK;
686 #ifdef HAVE_KRB5
687 krb5_error_code ret;
688 #endif
690 if (!entry) {
691 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
694 if (entry->ref_count <= 0) {
695 DEBUG(0,("remove_ccache: logic error. "
696 "ref count for user %s = %d\n",
697 username, entry->ref_count));
698 return NT_STATUS_INTERNAL_DB_CORRUPTION;
701 entry->ref_count--;
703 if (entry->ref_count > 0) {
704 DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
705 username, entry->ref_count));
706 return NT_STATUS_OK;
709 /* no references any more */
711 DLIST_REMOVE(ccache_list, entry);
712 TALLOC_FREE(entry->event); /* unregisters events */
714 #ifdef HAVE_KRB5
715 ret = ads_kdestroy(entry->ccname);
717 /* we ignore the error when there has been no credential cache */
718 if (ret == KRB5_FCC_NOFILE) {
719 ret = 0;
720 } else if (ret) {
721 DEBUG(0,("remove_ccache: "
722 "failed to destroy user krb5 ccache %s with: %s\n",
723 entry->ccname, error_message(ret)));
724 } else {
725 DEBUG(10,("remove_ccache: "
726 "successfully destroyed krb5 ccache %s for user %s\n",
727 entry->ccname, username));
729 status = krb5_to_nt_status(ret);
730 #endif
732 TALLOC_FREE(entry);
733 DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
735 return status;
738 /*******************************************************************
739 In memory credentials cache code.
740 *******************************************************************/
742 static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
744 /***********************************************************
745 Find an entry on the list by name.
746 ***********************************************************/
748 struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
750 struct WINBINDD_MEMORY_CREDS *p;
752 for (p = memory_creds_list; p; p = p->next) {
753 if (strequal(p->username, username)) {
754 return p;
757 return NULL;
760 /***********************************************************
761 Store the required creds and mlock them.
762 ***********************************************************/
764 static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
765 const char *pass)
767 #if !defined(HAVE_MLOCK)
768 return NT_STATUS_OK;
769 #else
770 /* new_entry->nt_hash is the base pointer for the block
771 of memory pointed into by new_entry->lm_hash and
772 new_entry->pass (if we're storing plaintext). */
774 memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
775 if (pass) {
776 memcredp->len += strlen(pass)+1;
780 #if defined(LINUX)
781 /* aligning the memory on on x86_64 and compiling
782 with gcc 4.1 using -O2 causes a segv in the
783 next memset() --jerry */
784 memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
785 #else
786 /* On non-linux platforms, mlock()'d memory must be aligned */
787 memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
788 getpagesize(), memcredp->len);
789 #endif
790 if (!memcredp->nt_hash) {
791 return NT_STATUS_NO_MEMORY;
793 memset(memcredp->nt_hash, 0x0, memcredp->len);
795 memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
797 #ifdef DEBUG_PASSWORD
798 DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
799 #endif
800 if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
801 DEBUG(0,("failed to mlock memory: %s (%d)\n",
802 strerror(errno), errno));
803 SAFE_FREE(memcredp->nt_hash);
804 return map_nt_error_from_unix(errno);
807 #ifdef DEBUG_PASSWORD
808 DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
809 #endif
811 /* Create and store the password hashes. */
812 E_md4hash(pass, memcredp->nt_hash);
813 E_deshash(pass, memcredp->lm_hash);
815 if (pass) {
816 memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
817 memcpy(memcredp->pass, pass,
818 memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
821 return NT_STATUS_OK;
822 #endif
825 /***********************************************************
826 Destroy existing creds.
827 ***********************************************************/
829 static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
831 #if !defined(HAVE_MUNLOCK)
832 return NT_STATUS_OK;
833 #else
834 if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
835 DEBUG(0,("failed to munlock memory: %s (%d)\n",
836 strerror(errno), errno));
837 return map_nt_error_from_unix(errno);
839 memset(memcredp->nt_hash, '\0', memcredp->len);
840 SAFE_FREE(memcredp->nt_hash);
841 memcredp->nt_hash = NULL;
842 memcredp->lm_hash = NULL;
843 memcredp->pass = NULL;
844 memcredp->len = 0;
845 return NT_STATUS_OK;
846 #endif
849 /***********************************************************
850 Replace the required creds with new ones (password change).
851 ***********************************************************/
853 static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
854 const char *pass)
856 NTSTATUS status = delete_memory_creds(memcredp);
857 if (!NT_STATUS_IS_OK(status)) {
858 return status;
860 return store_memory_creds(memcredp, pass);
863 /*************************************************************
864 Store credentials in memory in a list.
865 *************************************************************/
867 static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
868 uid_t uid,
869 const char *pass)
871 /* Shortcut to ensure we don't store if no mlock. */
872 #if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
873 return NT_STATUS_OK;
874 #else
875 NTSTATUS status;
876 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
878 memcredp = find_memory_creds_by_name(username);
879 if (uid == (uid_t)-1) {
880 DEBUG(0,("winbindd_add_memory_creds_internal: "
881 "invalid uid for user %s.\n", username));
882 return NT_STATUS_INVALID_PARAMETER;
885 if (memcredp) {
886 /* Already exists. Increment the reference count and replace stored creds. */
887 if (uid != memcredp->uid) {
888 DEBUG(0,("winbindd_add_memory_creds_internal: "
889 "uid %u for user %s doesn't "
890 "match stored uid %u. Replacing.\n",
891 (unsigned int)uid, username,
892 (unsigned int)memcredp->uid));
893 memcredp->uid = uid;
895 memcredp->ref_count++;
896 DEBUG(10,("winbindd_add_memory_creds_internal: "
897 "ref count for user %s is now %d\n",
898 username, memcredp->ref_count));
899 return winbindd_replace_memory_creds_internal(memcredp, pass);
902 memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
903 if (!memcredp) {
904 return NT_STATUS_NO_MEMORY;
906 memcredp->username = talloc_strdup(memcredp, username);
907 if (!memcredp->username) {
908 talloc_destroy(memcredp);
909 return NT_STATUS_NO_MEMORY;
912 status = store_memory_creds(memcredp, pass);
913 if (!NT_STATUS_IS_OK(status)) {
914 talloc_destroy(memcredp);
915 return status;
918 memcredp->uid = uid;
919 memcredp->ref_count = 1;
920 DLIST_ADD(memory_creds_list, memcredp);
922 DEBUG(10,("winbindd_add_memory_creds_internal: "
923 "added entry for user %s\n", username));
925 return NT_STATUS_OK;
926 #endif
929 /*************************************************************
930 Store users credentials in memory. If we also have a
931 struct WINBINDD_CCACHE_ENTRY for this username with a
932 refresh timer, then store the plaintext of the password
933 and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
934 *************************************************************/
936 NTSTATUS winbindd_add_memory_creds(const char *username,
937 uid_t uid,
938 const char *pass)
940 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
941 NTSTATUS status;
943 status = winbindd_add_memory_creds_internal(username, uid, pass);
944 if (!NT_STATUS_IS_OK(status)) {
945 return status;
948 if (entry) {
949 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
950 memcredp = find_memory_creds_by_name(username);
951 if (memcredp) {
952 entry->cred_ptr = memcredp;
956 return status;
959 /*************************************************************
960 Decrement the in-memory ref count - delete if zero.
961 *************************************************************/
963 NTSTATUS winbindd_delete_memory_creds(const char *username)
965 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
966 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
967 NTSTATUS status = NT_STATUS_OK;
969 memcredp = find_memory_creds_by_name(username);
970 entry = get_ccache_by_username(username);
972 if (!memcredp) {
973 DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
974 username));
975 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
978 if (memcredp->ref_count <= 0) {
979 DEBUG(0,("winbindd_delete_memory_creds: logic error. "
980 "ref count for user %s = %d\n",
981 username, memcredp->ref_count));
982 status = NT_STATUS_INTERNAL_DB_CORRUPTION;
985 memcredp->ref_count--;
986 if (memcredp->ref_count <= 0) {
987 delete_memory_creds(memcredp);
988 DLIST_REMOVE(memory_creds_list, memcredp);
989 talloc_destroy(memcredp);
990 DEBUG(10,("winbindd_delete_memory_creds: "
991 "deleted entry for user %s\n",
992 username));
993 } else {
994 DEBUG(10,("winbindd_delete_memory_creds: "
995 "entry for user %s ref_count now %d\n",
996 username, memcredp->ref_count));
999 if (entry) {
1000 /* Ensure we have no dangling references to this. */
1001 entry->cred_ptr = NULL;
1004 return status;
1007 /***********************************************************
1008 Replace the required creds with new ones (password change).
1009 ***********************************************************/
1011 NTSTATUS winbindd_replace_memory_creds(const char *username,
1012 const char *pass)
1014 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1016 memcredp = find_memory_creds_by_name(username);
1017 if (!memcredp) {
1018 DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
1019 username));
1020 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1023 DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
1024 username));
1026 return winbindd_replace_memory_creds_internal(memcredp, pass);