/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- get_ccache_by_username
- ccache_entry_count
- ccache_remove_all_after_fork
- krb5_ticket_refresh_handler
- krb5_ticket_gain_handler
- add_krb5_ticket_gain_handler_event
- ccache_regain_all_now
- ccache_entry_exists
- ccache_entry_identical
- add_ccache_to_list
- remove_ccache
- find_memory_creds_by_name
- store_memory_creds
- delete_memory_creds
- winbindd_replace_memory_creds_internal
- winbindd_add_memory_creds_internal
- winbindd_add_memory_creds
- winbindd_delete_memory_creds
- winbindd_replace_memory_creds
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 #undef DBGC_CLASS
27 #define DBGC_CLASS DBGC_WINBIND
28
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
33
34 #define MAX_CCACHES 100
35
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);
43
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))
48
49 /****************************************************************
50 Find an entry by name.
51 ****************************************************************/
52
53 static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
/* [<][>][^][v][top][bottom][index][help] */
54 {
55 struct WINBINDD_CCACHE_ENTRY *entry;
56
57 for (entry = ccache_list; entry; entry = entry->next) {
58 if (strequal(entry->username, username)) {
59 return entry;
60 }
61 }
62 return NULL;
63 }
64
65 /****************************************************************
66 How many do we have ?
67 ****************************************************************/
68
69 static int ccache_entry_count(void)
/* [<][>][^][v][top][bottom][index][help] */
70 {
71 struct WINBINDD_CCACHE_ENTRY *entry;
72 int i = 0;
73
74 for (entry = ccache_list; entry; entry = entry->next) {
75 i++;
76 }
77 return i;
78 }
79
80 void ccache_remove_all_after_fork(void)
/* [<][>][^][v][top][bottom][index][help] */
81 {
82 struct WINBINDD_CCACHE_ENTRY *cur, *next;
83
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);
89 }
90
91 return;
92 }
93
94 /****************************************************************
95 Do the work of refreshing the ticket.
96 ****************************************************************/
97
98 static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
/* [<][>][^][v][top][bottom][index][help] */
99 struct timed_event *te,
100 struct timeval now,
101 void *private_data)
102 {
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
111
112 DEBUG(10,("krb5_ticket_refresh_handler called\n"));
113 DEBUGADD(10,("event called for: %s, %s\n",
114 entry->ccname, entry->username));
115
116 TALLOC_FREE(entry->event);
117
118 #ifdef HAVE_KRB5
119
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. */
124
125 if (entry->renew_until < time(NULL)) {
126 rekinit:
127 if (cred_ptr && cred_ptr->pass) {
128
129 set_effective_uid(entry->uid);
130
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();
142
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);
150
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 * */
156
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;
168 }
169 TALLOC_FREE(entry->event);
170 return;
171 }
172
173 DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
174 "for: %s in ccache: %s\n",
175 entry->principal_name, entry->ccname));
176
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;
194 }
195 }
196
197 set_effective_uid(entry->uid);
198
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
209
210 gain_root_privilege();
211
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 */
217
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 */
222
223 ads_kdestroy(entry->ccname);
224
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? */
230
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;
244 }
245
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.
249 *
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;
260
261 return;
262 }
263
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;
274 }
275
276 if (entry->refresh_time == 0) {
277 entry->refresh_time = new_start;
278 }
279 entry->event = event_add_timed(winbind_event_context(), entry,
280 timeval_set(new_start, 0),
281 krb5_ticket_refresh_handler,
282 entry);
283
284 #endif
285 }
286
287 /****************************************************************
288 Do the work of regaining a ticket when coming from offline auth.
289 ****************************************************************/
290
291 static void krb5_ticket_gain_handler(struct event_context *event_ctx,
/* [<][>][^][v][top][bottom][index][help] */
292 struct timed_event *te,
293 struct timeval now,
294 void *private_data)
295 {
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
304
305 DEBUG(10,("krb5_ticket_gain_handler called\n"));
306 DEBUGADD(10,("event called for: %s, %s\n",
307 entry->ccname, entry->username));
308
309 TALLOC_FREE(entry->event);
310
311 #ifdef HAVE_KRB5
312
313 if (!cred_ptr || !cred_ptr->pass) {
314 DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
315 return;
316 }
317
318 if ((domain = find_domain_from_name(entry->realm)) == NULL) {
319 DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
320 return;
321 }
322
323 if (!domain->online) {
324 goto retry_later;
325 }
326
327 set_effective_uid(entry->uid);
328
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();
340
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;
349 }
350
351 DEBUG(10,("krb5_ticket_gain_handler: "
352 "successful kinit for: %s in ccache: %s\n",
353 entry->principal_name, entry->ccname));
354
355 goto got_ticket;
356
357 retry_later:
358
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
364
365 add_krb5_ticket_gain_handler_event(entry, t);
366 return;
367
368 got_ticket:
369
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
375
376 if (entry->refresh_time == 0) {
377 entry->refresh_time = t.tv_sec;
378 }
379 entry->event = event_add_timed(winbind_event_context(),
380 entry,
381 t,
382 krb5_ticket_refresh_handler,
383 entry);
384
385 return;
386 #endif
387 }
388
389 /**************************************************************
390 The gain initial ticket case is recognised as entry->refresh_time
391 is always zero.
392 **************************************************************/
393
394 static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
/* [<][>][^][v][top][bottom][index][help] */
395 struct timeval t)
396 {
397 entry->refresh_time = 0;
398 entry->event = event_add_timed(winbind_event_context(),
399 entry,
400 t,
401 krb5_ticket_gain_handler,
402 entry);
403 }
404
405 void ccache_regain_all_now(void)
/* [<][>][^][v][top][bottom][index][help] */
406 {
407 struct WINBINDD_CCACHE_ENTRY *cur;
408 struct timeval t = timeval_current();
409
410 for (cur = ccache_list; cur; cur = cur->next) {
411 struct timed_event *new_event;
412
413 /*
414 * if refresh_time is 0, we know that the
415 * the event has the krb5_ticket_gain_handler
416 */
417 if (cur->refresh_time == 0) {
418 new_event = event_add_timed(winbind_event_context(),
419 cur,
420 t,
421 krb5_ticket_gain_handler,
422 cur);
423 } else {
424 new_event = event_add_timed(winbind_event_context(),
425 cur,
426 t,
427 krb5_ticket_refresh_handler,
428 cur);
429 }
430
431 if (!new_event) {
432 continue;
433 }
434
435 TALLOC_FREE(cur->event);
436 cur->event = new_event;
437 }
438
439 return;
440 }
441
442 /****************************************************************
443 Check if an ccache entry exists.
444 ****************************************************************/
445
446 bool ccache_entry_exists(const char *username)
/* [<][>][^][v][top][bottom][index][help] */
447 {
448 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
449 return (entry != NULL);
450 }
451
452 /****************************************************************
453 Ensure we're changing the correct entry.
454 ****************************************************************/
455
456 bool ccache_entry_identical(const char *username,
/* [<][>][^][v][top][bottom][index][help] */
457 uid_t uid,
458 const char *ccname)
459 {
460 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
461
462 if (!entry) {
463 return False;
464 }
465
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;
470 }
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;
476 }
477 return True;
478 }
479
480 NTSTATUS add_ccache_to_list(const char *princ_name,
/* [<][>][^][v][top][bottom][index][help] */
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)
490 {
491 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
492 struct timeval t;
493 NTSTATUS ntret;
494 #ifdef HAVE_KRB5
495 int ret;
496 #endif
497
498 if ((username == NULL && princ_name == NULL) ||
499 ccname == NULL || uid < 0) {
500 return NT_STATUS_INVALID_PARAMETER;
501 }
502
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;
507 }
508
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;
517 }
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));
527 }
528 }
529 #endif
530
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;
537 }
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 */
545
546 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
547 return NT_STATUS_OK;
548 }
549
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;
563 }
564 entry->event = event_add_timed(winbind_event_context(),
565 entry,
566 t,
567 krb5_ticket_refresh_handler,
568 entry);
569 }
570
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;
580 }
581 return NT_STATUS_NO_MEMORY;
582 }
583
584 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
585 }
586
587 return NT_STATUS_OK;
588 }
589
590 entry = TALLOC_P(NULL, struct WINBINDD_CCACHE_ENTRY);
591 if (!entry) {
592 return NT_STATUS_NO_MEMORY;
593 }
594
595 ZERO_STRUCTP(entry);
596
597 if (username) {
598 entry->username = talloc_strdup(entry, username);
599 if (!entry->username) {
600 goto no_mem;
601 }
602 }
603 if (princ_name) {
604 entry->principal_name = talloc_strdup(entry, princ_name);
605 if (!entry->principal_name) {
606 goto no_mem;
607 }
608 }
609 if (service) {
610 entry->service = talloc_strdup(entry, service);
611 if (!entry->service) {
612 goto no_mem;
613 }
614 }
615
616 entry->ccname = talloc_strdup(entry, ccname);
617 if (!entry->ccname) {
618 goto no_mem;
619 }
620
621 entry->realm = talloc_strdup(entry, realm);
622 if (!entry->realm) {
623 goto no_mem;
624 }
625
626 entry->create_time = create_time;
627 entry->renew_until = renew_until;
628 entry->uid = uid;
629 entry->ref_count = 1;
630
631 if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
632 goto add_entry;
633 }
634
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;
647 }
648 entry->event = event_add_timed(winbind_event_context(),
649 entry,
650 t,
651 krb5_ticket_refresh_handler,
652 entry);
653 }
654
655 if (!entry->event) {
656 goto no_mem;
657 }
658
659 DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
660
661 add_entry:
662
663 DLIST_ADD(ccache_list, entry);
664
665 DEBUG(10,("add_ccache_to_list: "
666 "added ccache [%s] for user [%s] to the list\n",
667 ccname, username));
668
669 return NT_STATUS_OK;
670
671 no_mem:
672
673 TALLOC_FREE(entry);
674 return NT_STATUS_NO_MEMORY;
675 }
676
677 /*******************************************************************
678 Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
679 referenced.
680 *******************************************************************/
681
682 NTSTATUS remove_ccache(const char *username)
/* [<][>][^][v][top][bottom][index][help] */
683 {
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
689
690 if (!entry) {
691 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
692 }
693
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;
699 }
700
701 entry->ref_count--;
702
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;
707 }
708
709 /* no references any more */
710
711 DLIST_REMOVE(ccache_list, entry);
712 TALLOC_FREE(entry->event); /* unregisters events */
713
714 #ifdef HAVE_KRB5
715 ret = ads_kdestroy(entry->ccname);
716
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));
728 }
729 status = krb5_to_nt_status(ret);
730 #endif
731
732 TALLOC_FREE(entry);
733 DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
734
735 return status;
736 }
737
738 /*******************************************************************
739 In memory credentials cache code.
740 *******************************************************************/
741
742 static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
743
744 /***********************************************************
745 Find an entry on the list by name.
746 ***********************************************************/
747
748 struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
/* [<][>][^][v][top][bottom][index][help] */
749 {
750 struct WINBINDD_MEMORY_CREDS *p;
751
752 for (p = memory_creds_list; p; p = p->next) {
753 if (strequal(p->username, username)) {
754 return p;
755 }
756 }
757 return NULL;
758 }
759
760 /***********************************************************
761 Store the required creds and mlock them.
762 ***********************************************************/
763
764 static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
/* [<][>][^][v][top][bottom][index][help] */
765 const char *pass)
766 {
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). */
773
774 memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
775 if (pass) {
776 memcredp->len += strlen(pass)+1;
777 }
778
779
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;
792 }
793 memset(memcredp->nt_hash, 0x0, memcredp->len);
794
795 memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
796
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);
805 }
806
807 #ifdef DEBUG_PASSWORD
808 DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
809 #endif
810
811 /* Create and store the password hashes. */
812 E_md4hash(pass, memcredp->nt_hash);
813 E_deshash(pass, memcredp->lm_hash);
814
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);
819 }
820
821 return NT_STATUS_OK;
822 #endif
823 }
824
825 /***********************************************************
826 Destroy existing creds.
827 ***********************************************************/
828
829 static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
/* [<][>][^][v][top][bottom][index][help] */
830 {
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);
838 }
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
847 }
848
849 /***********************************************************
850 Replace the required creds with new ones (password change).
851 ***********************************************************/
852
853 static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
/* [<][>][^][v][top][bottom][index][help] */
854 const char *pass)
855 {
856 NTSTATUS status = delete_memory_creds(memcredp);
857 if (!NT_STATUS_IS_OK(status)) {
858 return status;
859 }
860 return store_memory_creds(memcredp, pass);
861 }
862
863 /*************************************************************
864 Store credentials in memory in a list.
865 *************************************************************/
866
867 static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
/* [<][>][^][v][top][bottom][index][help] */
868 uid_t uid,
869 const char *pass)
870 {
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;
877
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;
883 }
884
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;
894 }
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);
900 }
901
902 memcredp = TALLOC_ZERO_P(NULL, struct WINBINDD_MEMORY_CREDS);
903 if (!memcredp) {
904 return NT_STATUS_NO_MEMORY;
905 }
906 memcredp->username = talloc_strdup(memcredp, username);
907 if (!memcredp->username) {
908 talloc_destroy(memcredp);
909 return NT_STATUS_NO_MEMORY;
910 }
911
912 status = store_memory_creds(memcredp, pass);
913 if (!NT_STATUS_IS_OK(status)) {
914 talloc_destroy(memcredp);
915 return status;
916 }
917
918 memcredp->uid = uid;
919 memcredp->ref_count = 1;
920 DLIST_ADD(memory_creds_list, memcredp);
921
922 DEBUG(10,("winbindd_add_memory_creds_internal: "
923 "added entry for user %s\n", username));
924
925 return NT_STATUS_OK;
926 #endif
927 }
928
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 *************************************************************/
935
936 NTSTATUS winbindd_add_memory_creds(const char *username,
/* [<][>][^][v][top][bottom][index][help] */
937 uid_t uid,
938 const char *pass)
939 {
940 struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
941 NTSTATUS status;
942
943 status = winbindd_add_memory_creds_internal(username, uid, pass);
944 if (!NT_STATUS_IS_OK(status)) {
945 return status;
946 }
947
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;
953 }
954 }
955
956 return status;
957 }
958
959 /*************************************************************
960 Decrement the in-memory ref count - delete if zero.
961 *************************************************************/
962
963 NTSTATUS winbindd_delete_memory_creds(const char *username)
/* [<][>][^][v][top][bottom][index][help] */
964 {
965 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
966 struct WINBINDD_CCACHE_ENTRY *entry = NULL;
967 NTSTATUS status = NT_STATUS_OK;
968
969 memcredp = find_memory_creds_by_name(username);
970 entry = get_ccache_by_username(username);
971
972 if (!memcredp) {
973 DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
974 username));
975 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
976 }
977
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;
983 }
984
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));
997 }
998
999 if (entry) {
1000 /* Ensure we have no dangling references to this. */
1001 entry->cred_ptr = NULL;
1002 }
1003
1004 return status;
1005 }
1006
1007 /***********************************************************
1008 Replace the required creds with new ones (password change).
1009 ***********************************************************/
1010
1011 NTSTATUS winbindd_replace_memory_creds(const char *username,
/* [<][>][^][v][top][bottom][index][help] */
1012 const char *pass)
1013 {
1014 struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1015
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;
1021 }
1022
1023 DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
1024 username));
1025
1026 return winbindd_replace_memory_creds_internal(memcredp, pass);
1027 }